{"id":5376,"date":"2026-08-30T17:10:42","date_gmt":"2026-08-30T08:10:42","guid":{"rendered":"https:\/\/donguri3.net\/server-tech\/times-car-n8n-davical-sync-logic-2\/"},"modified":"2026-08-30T17:10:43","modified_gmt":"2026-08-30T08:10:43","slug":"times-car-n8n-davical-sync-logic","status":"publish","type":"post","link":"https:\/\/donguri3.net\/en\/server-tech\/n8n-automation\/times-car-n8n-davical-sync-logic\/","title":{"rendered":"Full Synchronization of Times Car Reservations to Your Own Calendar! Building a &#8220;Forgot-to-Cancel Prevention&#8221; Feature with n8n"},"content":{"rendered":"<p>The other day, I had a bitter experience where I forgot to cancel a Times Car reservation and ended up being charged several thousand yen in usage fees despite never even driving the car. The cause was simple human error: I completely forgot to put the reservation itself into my calendar, and it was entirely erased from my brain.<\/p>\n<p>&#8220;I will never repeat this wasteful expense again.&#8221;<\/p>\n<p>With that resolve, I decided to build a system that reflects reservation status in my smartphone calendar in real-time. While this kind of automation is possible using iPhone &#8220;Shortcuts&#8221; or automation features, I deliberately chose to solve it server-side using n8n.<\/p>\n<h2>Why the Choice of &#8220;n8n&#8221;?<\/h2>\n<p>There is a solid, practical reason for &#8220;not keeping everything confined to a smartphone.&#8221;<\/p>\n<ul>\n<li>No device constraints: Even if the smartphone&#8217;s battery dies or it&#8217;s out of service range, the server monitors emails 24\/7 and completes the processing.<\/li>\n<li>Affinity with existing infrastructure (DAViCal): I wanted to stream data directly into the calendar server (DAViCal) that I already operate at home.<\/li>\n<li>Advanced conditional branching: It&#8217;s not just simple &#8220;registration.&#8221; n8n&#8217;s flexible logic was indispensable for parsing complex email formats\u2014determining actual usage times from &#8220;reservation changes,&#8221; &#8220;cancellations,&#8221; and &#8220;return receipts,&#8221; and even handling &#8220;rental unavailibility notifications due to vehicle trouble&#8221; to rewrite the calendar accordingly.<\/li>\n<\/ul>\n<h2>Setting Up the n8n Environment (docker-compose.yml)<\/h2>\n<p>First, we launch n8n using Docker. As explained in Part 1, set N8N_HOST to localhost only during OAuth authentication, and revert it back to the server&#8217;s static IP after authentication is complete.<\/p>\n<pre class=\"brush: yaml; title: ; notranslate\" title=\"\">services:\nn8n:\nimage: n8nio\/n8n:latest\ncontainer_name: n8n\nrestart: always\nports:\n- &quot;5678:5678&quot;\nenvironment:\n- N8N_HOST=localhost # Change to static IP after authentication\n- N8N_PORT=5678\n- N8N_PROTOCOL=http\n- NODE_ENV=production\n- GENERIC_TIMEZONE=Asia\/Tokyo\n- WEBHOOK_URL=http:\/\/localhost:5678\/ # Change to static IP after authentication\n- N8N_SECURE_COOKIE=false;\nvolumes:\n- .\/n8n_data:\/home\/node\/.n8n<\/pre>\n<h2>Initial Setup of n8n<\/h2>\n<p>Once the container starts, access http:\/\/&lt;n8n-server&gt;:5678 in your browser. An account creation screen will appear first, but for personal use, proceed with the following steps.<\/p>\n<ol>\n<li>Account Creation: Set your email address and password.<\/li>\n<li>Survey Screen: A question appears: &#8220;What best describes your company?&#8221; (What best describes your organization?)<\/li>\n<ul>\n<li>Here, just select &#8220;I&#8217;m not using n8n for work&#8221;.<br \/>\n<a href=\"https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/bcd5227d80d80f5e8703dfc7b9bc6229.png\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-2800\" src=\"https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/bcd5227d80d80f5e8703dfc7b9bc6229.png\" alt=\"I'm not using n8n for work\" width=\"478\" height=\"363\" srcset=\"https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/bcd5227d80d80f5e8703dfc7b9bc6229.png 478w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/bcd5227d80d80f5e8703dfc7b9bc6229-300x228.png 300w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/bcd5227d80d80f5e8703dfc7b9bc6229-64x49.png 64w\" sizes=\"(max-width: 478px) 100vw, 478px\" \/><\/a><\/li>\n<\/ul>\n<\/li>\n<li>Start: Clicking the &#8220;Get started&#8221; button will display the main screen (canvas).<\/li>\n<li>Begin Creation: Click &#8220;Create Workflow&#8221; and start drawing your very own automation pipeline!<\/li>\n<\/ol>\n<p><a href=\"https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/3c0ac8b1955def9fff8c1c162ab29bee.png\"><img decoding=\"async\" class=\"aligncenter size-large wp-image-2801\" src=\"https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/3c0ac8b1955def9fff8c1c162ab29bee-1024x324.png\" alt=\"n8n overview\" width=\"1024\" height=\"324\" srcset=\"https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/3c0ac8b1955def9fff8c1c162ab29bee-1024x324.png 1024w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/3c0ac8b1955def9fff8c1c162ab29bee-300x95.png 300w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/3c0ac8b1955def9fff8c1c162ab29bee-768x243.png 768w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/3c0ac8b1955def9fff8c1c162ab29bee-530x168.png 530w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/3c0ac8b1955def9fff8c1c162ab29bee-565x179.png 565w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/3c0ac8b1955def9fff8c1c162ab29bee-710x225.png 710w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/3c0ac8b1955def9fff8c1c162ab29bee-725x229.png 725w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/3c0ac8b1955def9fff8c1c162ab29bee-64x20.png 64w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/3c0ac8b1955def9fff8c1c162ab29bee.png 1520w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><\/p>\n<h2>Workflow Design Philosophy<\/h2>\n<p>The parsing scripts are separated into &#8220;Standard Use&#8221; and &#8220;Accident\/Trouble Use&#8221; depending on the email subject (because the formats for reservation numbers and other details differ).<\/p>\n<ol>\n<li>Gmail API: Check for new emails every 1 minute.<\/li>\n<li>Switch Node: Branch based on the title.\n<ul>\n<li>&#8220;There is a possibility that the reserved vehicle cannot be used&#8221; \u2192 Accident route<\/li>\n<li>Everything else (registration, modification, cancellation, return) \u2192 Standard route<\/li>\n<\/ul>\n<\/li>\n<li>JavaScript Node: Extract date\/time and reservation numbers according to each format.<\/li>\n<li>HTTP Request: Execute PUT (create\/update) or DELETE (delete) to DAViCal.<\/li>\n<\/ol>\n<p><a href=\"https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/f4b3c8f6a6c48aa909b5d94610c3edb5.png\"><img decoding=\"async\" class=\"aligncenter size-large wp-image-2798\" src=\"https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/f4b3c8f6a6c48aa909b5d94610c3edb5-1024x232.png\" alt=\"workflows\" width=\"1024\" height=\"232\" srcset=\"https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/f4b3c8f6a6c48aa909b5d94610c3edb5-1024x232.png 1024w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/f4b3c8f6a6c48aa909b5d94610c3edb5-300x68.png 300w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/f4b3c8f6a6c48aa909b5d94610c3edb5-768x174.png 768w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/f4b3c8f6a6c48aa909b5d94610c3edb5-530x120.png 530w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/f4b3c8f6a6c48aa909b5d94610c3edb5-565x128.png 565w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/f4b3c8f6a6c48aa909b5d94610c3edb5-710x161.png 710w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/f4b3c8f6a6c48aa909b5d94610c3edb5-725x164.png 725w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/f4b3c8f6a6c48aa909b5d94610c3edb5-64x15.png 64w, https:\/\/donguri3.net\/wp-content\/uploads\/2026\/03\/f4b3c8f6a6c48aa909b5d94610c3edb5.png 1248w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><\/p>\n<h2>Parsing Processing Using JavaScript<\/h2>\n<p>This is the core heart of generating the calendar format (.ics) from the email body.<\/p>\n<h3>[For Standard Route] Managing Reservations, Modifications, Cancellations, and Returns<\/h3>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\n\nconst items = $input.all();\n\nreturn items.map(item =&amp;gt; {\n\/\/ Retrieve text or textPlain depending on Gmail output\nconst body = item.json.text || item.json.textPlain || &quot;&quot;;\nconst subject = item.json.subject || &quot;&quot;;\n\n\/\/ 1. Common: Extract reservation number\nconst resIdMatch = body.match(\/\u25a0\u4e88\u7d04\u756a\u53f7\n(\\d+)\/);\nconst resId = resIdMatch ? resIdMatch&#x5B;1] : &quot;000000&quot;;\n\n\/\/ 2. Determine action\nlet action = &quot;PUT&quot;;\nif (subject.includes(&quot;\u4e88\u7d04\u53d6\u6d88\u5b8c\u4e86&quot;)) {\naction = &quot;DELETE&quot;;\n}\n\n\/\/ 3. Extract data (only for PUT)\nlet startTime = &quot;&quot;, endTime = &quot;&quot;, stationName = &quot;\u30bf\u30a4\u30e0\u30ba\u30ab\u30fc&quot;, stationUrl = &quot;&quot;, carModel = &quot;&quot;;\n\nif (action === &quot;PUT&quot;) {\n\/\/ Extract vehicle\nconst carMatch = body.match(\/\u25a0\u8eca\u4e21\n(&#x5B;^\n]+)\/);\ncarModel = carMatch ? carMatch&#x5B;1].trim() : &quot;&quot;;\n\n\/\/ Separate station name and URL\nconst stationMatch = body.match(\/\u25a0\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\n(&#x5B;^\n]+)\n(https?:\\\/\\\/&#x5B;^\n]+)\/);\nif (stationMatch) {\nstationName = stationMatch&#x5B;1].trim();\nstationUrl = stationMatch&#x5B;2].trim();\n}\n\n\/\/ [Fix Point 1] Date\/time parsing helper (modified to insert T)\nconst formatIcsDate = (str) =&amp;gt; {\nif (!str) return &quot;&quot;;\nconst clean = str.replace(\/&#x5B;\\\/ :]\/g, &#039;&#039;); \/\/ 202603141000\nreturn clean.slice(0, 8) + &#039;T&#039; + clean.slice(8, 12) + &#039;00&#039;; \/\/ 20260314T100000\n};\n\nif (subject.includes(&quot;\u8fd4\u5374\u8a3c&quot;)) {\nconst timeMatch = body.match(\/\u25a0\u5229\u7528\u6642\u9593\n(\\d{4}\\\/\\d{2}\\\/\\d{2} \\d{2}:\\d{2}) - (\\d{4}\\\/\\d{2}\\\/\\d{2} \\d{2}:\\d{2})\/);\nif (timeMatch) {\nstartTime = formatIcsDate(timeMatch&#x5B;1]);\nendTime = formatIcsDate(timeMatch&#x5B;2]);\n}\n} else {\nconst startMatch = body.match(\/\u25a0\u5229\u7528\u958b\u59cb\u65e5\u6642\n(\\d{4}\\\/\\d{2}\\\/\\d{2} \\d{2}:\\d{2})\/);\nconst endMatch = body.match(\/\u25a0\u8fd4\u5374\u4e88\u5b9a\u65e5\u6642\n(\\d{4}\\\/\\d{2}\\\/\\d{2} \\d{2}:\\d{2})\/);\nstartTime = startMatch ? formatIcsDate(startMatch&#x5B;1]) : &quot;&quot;;\nendTime = endMatch ? formatIcsDate(endMatch&#x5B;1]) : &quot;&quot;;\n}\n}\n\n\/\/ 4. Build iCalendar data\nconst summaryPrefix = subject.includes(&quot;\u8fd4\u5374\u8a3c&quot;) ? &quot;\u3010\u8fd4\u5374\u6e08\u3011&quot; : &quot;\u3010\u4e88\u7d04\u3011&quot;;\nconst summary = `Times:${summaryPrefix}${stationName}`;\nconst icsDataRaw = `BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-\/\/Nando Kobo\/\/n8n\/\/EN\nBEGIN:VEVENT\nUID:times-${resId}\nSUMMARY:${summary}\nDTSTART:${startTime}\nDTEND:${endTime}\nLOCATION:${stationName}\nDESCRIPTION:\u8eca\u4e21: ${carModel}\\\n\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3: ${stationName}\nURL:${stationUrl}\nEND:VEVENT\nEND:VCALENDAR`;\n\n\/\/ [Fix Point 2] Batch replace line break codes from \n to \\r\n (CRLF)\nconst icsData = icsDataRaw.replace(\/\n\/g, &#039;\\r\n&#039;);\n\nreturn {\njson: {\naction,\nresId,\nfileName: `times-${resId}.ics`,\nicsData,\nisCancel: action === &quot;DELETE&quot;\n}\n};\n});<\/pre>\n<h3>[For Accident\/Trouble Route] Visualizing Emergencies<\/h3>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\n\nconst items = $input.all();\n\nreturn items.map(item =&amp;gt; {\nconst body = item.json.text || item.json.textPlain || &quot;&quot;;\n\n\/\/ 1. Extract reservation number\nconst resIdMatch = body.match(\/\u3010\u4e88\u7d04\u756a\u53f7\u3011(\\d+)\/);\nconst resId = resIdMatch ? resIdMatch&#x5B;1] : &quot;000000&quot;;\n\n\/\/ 2. Station and vehicle\nconst stationMatch = body.match(\/\u3010\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u3011(&#x5B;^\n]+)\/);\nconst stationName = stationMatch ? stationMatch&#x5B;1].trim() : &quot;\u30bf\u30a4\u30e0\u30ba\u30ab\u30fc&quot;;\n\nconst carMatch = body.match(\/\u3010\u8eca\u4e21\u3011(&#x5B;^\n]+)\/);\nconst carModel = carMatch ? carMatch&#x5B;1].trim() : &quot;\u4e0d\u660e&quot;;\n\n\/\/ 3. Special date\/time parsing (2026\/03\/20 08:00 \uff5e 2026\/03\/21 22:00)\nconst formatIcsDate = (str) =&amp;gt; {\nconst clean = str.replace(\/&#x5B;\\\/ :]\/g, &#039;&#039;);\nreturn clean.slice(0, 8) + &#039;T&#039; + clean.slice(8, 12) + &#039;00&#039;;\n};\n\nconst timeMatch = body.match(\/\u3010\u4e88\u7d04\u65e5\u6642\u3011(\\d{4}\\\/\\d{2}\\\/\\d{2} \\d{2}:\\d{2}) \uff5e (\\d{4}\\\/\\d{2}\\\/\\d{2} \\d{2}:\\d{2})\/);\nconst startTime = timeMatch ? formatIcsDate(timeMatch&#x5B;1]) : &quot;&quot;;\nconst endTime = timeMatch ? formatIcsDate(timeMatch&#x5B;2]) : &quot;&quot;;\n\n\/\/ 4. Build iCalendar data\nconst summary = `\u3010\u4e8b\u6545!!\u3011${stationName}`;\nconst stationUrl = &quot;https:\/\/share.timescar.jp\/view\/sp\/reserve\/list.jsp&quot;;\nconst description = `\u203b\u8eca\u4e21\u30c8\u30e9\u30d6\u30eb\u767a\u751f\u4e2d\uff01\u5225\u8eca\u4e21\u3078\u306e\u5909\u66f4\u304c\u5fc5\u8981\u3067\u3059\u3002\\\n\u8eca\u4e21: ${carModel}\\\n\u4e88\u7d04\u756a\u53f7: ${resId}`;\n\nconst icsData = `BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-\/\/Nando Kobo\/\/n8n\/\/EN\nBEGIN:VEVENT\nUID:times-${resId}\nSUMMARY:${summary}\nDTSTART:${startTime}\nDTEND:${endTime}\nLOCATION:${stationName}\nDESCRIPTION:${description}\nURL:${stationUrl}\nEND:VEVENT\nEND:VCALENDAR`.replace(\/\n\/g, &#039;\\r\n&#039;);\n\nreturn {\njson: {\naction: &quot;PUT&quot;,\nresId,\nfileName: `times-${resId}.ics`,\nicsData,\nisCancel: false\n}\n};\n});<\/pre>\n<h2>Countermeasures Against the &#8220;Fastidiousness&#8221; of the iCalendar Specification<\/h2>\n<p>Here is the prescription for two technical traps faced during writing to DAViCal:<\/p>\n<ul>\n<li>The date\/time &#8220;T&#8221;: Strictly adhere to the 20260314T100000 format (requiring a T in the middle).<\/li>\n<li>Line break codes: Convert all line breaks to CRLF using .replace(\/<br \/>\n\/g, &#8216;\\r<br \/>\n&#8217;). Missing this will result in an error (500) during DAViCal database registration.<\/li>\n<\/ul>\n<h2>Conclusion: If There Is No API, Just Build One<\/h2>\n<p>Times Car does not have an official API, but by parsing incoming emails, they can be transformed into a proper API.<\/p>\n<p>Through this build, my calendar has become a living log where &#8220;reservations, accidents, and returns&#8221; are automatically synchronized. Building a system exclusively for yourself in your own workshop\u2014this is the true essence of self-hosting.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The other day, I had a bitter experience where I forgot to cancel a Times Car reservation and ended up being c [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":2815,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_locale":"en_US","_original_post":"https:\/\/donguri3.net\/?p=2797","footnotes":""},"categories":[1167],"tags":[18,19,52,290,1107,10,1040],"class_list":["post-5376","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-n8n-automation","tag-docker","tag-docker-compose","tag-https","tag-json","tag-n8n","tag-server","tag-1040","en-US"],"_links":{"self":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts\/5376","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/comments?post=5376"}],"version-history":[{"count":1,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts\/5376\/revisions"}],"predecessor-version":[{"id":5379,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/posts\/5376\/revisions\/5379"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/media\/2815"}],"wp:attachment":[{"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/media?parent=5376"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/categories?post=5376"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/donguri3.net\/wp-json\/wp\/v2\/tags?post=5376"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}