{"id":527,"date":"2024-06-20T13:02:54","date_gmt":"2024-06-20T13:02:54","guid":{"rendered":"https:\/\/helsinki-tallinn.com\/fi\/?page_id=527"},"modified":"2026-02-24T10:00:20","modified_gmt":"2026-02-24T10:00:20","slug":"aikataulu","status":"publish","type":"page","link":"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/","title":{"rendered":"Tallinna Helsinki aikataulut"},"content":{"rendered":"\n<p><em>Live-aikataulu n\u00e4ytt\u00e4\u00e4 oletuksena 3 p\u00e4iv\u00e4\u00e4, jotta lista pysyy selke\u00e4n\u00e4. Voit vaihtaa p\u00e4iv\u00e4m\u00e4\u00e4r\u00e4\u00e4 ja tarkistaa mink\u00e4 tahansa p\u00e4iv\u00e4n l\u00e4hd\u00f6t ja saapumiset.<\/em><\/p>\n\n\n\n\n\n<ul class=\"wp-block-list\">\n\t\n\t<li><a href=\"#helsinkitallinn\">Helsingist\u00e4 Tallinnaan<\/a><\/li>\n\t\n\t\n\t<li><a href=\"#tallinnhelsinki\">Tallinnasta Helsinkiin<\/a><\/li>\n\t\n<\/ul>\n\n\n<p>\ud83c\uddec\ud83c\udde7 <strong>Vinkki:<\/strong> Jos klikkaat aikataulutaulukossa aluksen nime\u00e4, avautuu englanninkielinen sivu, jossa on lis\u00e4\u00e4 alukseen liittyv\u00e4\u00e4 tietoa (ja joissain tapauksissa my\u00f6s live-seuranta).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"helsinkitallinn\">Aikataulu Helsingist\u00e4 Tallinnaan<\/h2>\n\n\n\n<p>Alla on live-aikataulu: l\u00e4hd\u00f6t <a href=\"https:\/\/helsinki-tallinn.com\/fi\/helsingin-satama\/\">Helsingin satamasta<\/a> ja suunnitellut saapumiset Tallinnaan. Reitti on yksi It\u00e4meren vilkkaimmista ja kuljettaa vuosittain noin <strong>7-8 miljoonaa matkustajaa<\/strong>. Tyypillinen matka-aika on noin <strong>2-2,5 tuntia<\/strong> (operaattorista ja vuorosta riippuen).<\/p>\n\n\n<div class=\"tp-wrap\" data-config=\"{&quot;rest&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/fi\\\/wp-json\\\/timetables-pro\\\/v1\\\/timetables&quot;,&quot;route&quot;:161,&quot;days&quot;:3,&quot;autoload&quot;:true,&quot;label&quot;:&quot;Helsinki - Talinn&quot;,&quot;labels&quot;:{&quot;ui_date&quot;:&quot;Pvm&quot;,&quot;ui_days&quot;:&quot;P\\u00e4iv\\u00e4t&quot;,&quot;ui_button&quot;:&quot;N\\u00e4yt\\u00e4 l\\u00e4hd\\u00f6t&quot;,&quot;status_idle&quot;:&quot;Valitse pvm&quot;,&quot;status_loading&quot;:&quot;Lataa...&quot;,&quot;status_empty&quot;:&quot;Ei l\\u00e4ht\\u00f6j\\u00e4&quot;,&quot;status_found&quot;:&quot;%d l\\u00e4ht\\u00f6\\u00e4&quot;,&quot;th_date&quot;:&quot;Pvm&quot;,&quot;th_dep&quot;:&quot;L\\u00e4ht\\u00f6&quot;,&quot;th_arr&quot;:&quot;Saap.&quot;,&quot;th_dur&quot;:&quot;Kesto&quot;,&quot;th_ship&quot;:&quot;Laiva&quot;,&quot;th_op&quot;:&quot;Yhti\\u00f6&quot;,&quot;th_route&quot;:&quot;Reitti&quot;,&quot;wd_sun&quot;:&quot;Su&quot;,&quot;wd_mon&quot;:&quot;Ma&quot;,&quot;wd_tue&quot;:&quot;Ti&quot;,&quot;wd_wed&quot;:&quot;Ke&quot;,&quot;wd_thu&quot;:&quot;To&quot;,&quot;wd_fri&quot;:&quot;Pe&quot;,&quot;wd_sat&quot;:&quot;La&quot;,&quot;summary_footer_one&quot;:&quot;\\u00bb Kaikki %s l\\u00e4hd\\u00f6t&quot;,&quot;summary_footer_two&quot;:&quot;\\u00bb Lis\\u00e4\\u00e4 %1$s + %2$s&quot;,&quot;summary_footer_generic&quot;:&quot;Lis\\u00e4\\u00e4 l\\u00e4hd\\u00f6t&quot;,&quot;summary_header&quot;:&quot;Seuraavat 2 l\\u00e4ht\\u00f6\\u00e4:&quot;,&quot;summary_no_upcoming&quot;:&quot;Ei tulevia&quot;,&quot;more_link&quot;:&quot;Lis\\u00e4\\u00e4 l\\u00e4ht\\u00f6j\\u00e4&quot;},&quot;showLogo&quot;:true,&quot;shipMap&quot;:{&quot;fi&quot;:&quot;Finlandia&quot;},&quot;shipLinks&quot;:{&quot;fi&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/finlandia\\\/&quot;,&quot;viking xprs&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/viking-xprs\\\/&quot;,&quot;mystar&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/mystar\\\/&quot;,&quot;victoria i&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/victoria\\\/&quot;,&quot;megastar&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/megastar\\\/&quot;},&quot;showShip&quot;:true}\">\n  <div class=\"tp-controls\" role=\"group\" aria-label=\"Timetable controls\">\n    <div>\n      <label for=\"tp-date\">Pvm<\/label><br>\n      <input id=\"tp-date\" class=\"tp-date\" type=\"date\" aria-label=\"Pvm\">\n    <\/div>\n    <div>\n      <label for=\"tp-range\">P\u00e4iv\u00e4t<\/label><br>\n      <select id=\"tp-range\" class=\"tp-range\" aria-label=\"P\u00e4iv\u00e4t\">\n        <option value=\"1\">1<\/option><option value=\"3\">3<\/option><option value=\"7\">7<\/option><option value=\"14\">14<\/option>\n      <\/select>\n    <\/div>\n    <div><button id=\"tp-load\" class=\"tp-btn\">N\u00e4yt\u00e4 l\u00e4hd\u00f6t<\/button><\/div>\n  <\/div>\n\n  <div id=\"tp-status\" class=\"tp-muted\" aria-live=\"polite\">Valitse pvm<\/div>\n  <div id=\"tp-results\"><\/div>\n\n  <div class=\"tp-loader\" aria-hidden=\"true\">\n    <div class=\"tp-loader-card\">\n      <div class=\"tp-spinner\" aria-hidden=\"true\"><\/div>\n      <div class=\"tp-loader-text\">Lataa...<\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n  const wrap   = document.currentScript.previousElementSibling;\n  const cfg    = JSON.parse(wrap.getAttribute('data-config')||'{}');\n  const L      = cfg.labels||{};\n  const dateEl = wrap.querySelector('#tp-date');\n  const daysEl = wrap.querySelector('#tp-range');\n  const btn    = wrap.querySelector('#tp-load');\n  const status = wrap.querySelector('#tp-status');\n  const out    = wrap.querySelector('#tp-results');\n  const loader = wrap.querySelector('.tp-loader');\n\n  const showShip = (cfg.showShip !== false && cfg.showShip !== 0 && cfg.showShip !== '0');\n\n  dateEl.valueAsDate = new Date();\n  Array.from(daysEl.options).forEach(o=>{ if(parseInt(o.value,10)===parseInt(cfg.days||7,10)) o.selected=true; });\n\n  function pad(n){ return String(n).padStart(2,'0'); }\n  function iso(d){ return d.getFullYear()+'-'+pad(d.getMonth()+1)+'-'+pad(d.getDate()); }\n  function toLocal(s){ return new Date(s); }\n  function hhmm(d){ return d.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}); }\n  function dShort(d){ return d.toLocaleDateString([], {day:'numeric', month:'short'}); } \/\/ short, no year\n  function dur(m){ const h=Math.floor(m\/60), r=m%60; return r? (h+'h '+r+'m') : (h+'h'); }\n\n  const WD = [\n    L.wd_sun || 'SUN',\n    L.wd_mon || 'MON',\n    L.wd_tue || 'TUE',\n    L.wd_wed || 'WED',\n    L.wd_thu || 'THU',\n    L.wd_fri || 'FRI',\n    L.wd_sat || 'SAT'\n  ];\n\n  function setLoader(v){ loader.classList.toggle('show', !!v); loader.setAttribute('aria-hidden', v?'false':'true'); }\n\n  function opCell(r){\n    const name = r.opName || ('Operator '+(r.opId||''));\n    const logo = (cfg.showLogo && r.opLogo) ? '<img decoding=\"async\" src=\"'+r.opLogo+'\" alt=\"'+name+'\"> ' : '';\n    const label = logo + '<span>'+name+'<\/span>';\n    return r.opLink ? '<a class=\"tp-op\" href=\"'+r.opLink+'\" target=\"_blank\" rel=\"nofollow noopener\">'+label+'<\/a>' : '<span class=\"tp-op\">'+label+'<\/span>';\n  }\n\n  function shipInfo(original){\n    const key = (original||'').toLowerCase().trim();\n    const label = (cfg.shipMap && cfg.shipMap[key]) || original || '';\n    const href  = (cfg.shipLinks && cfg.shipLinks[key]) || '';\n    return {label, href};\n  }\n\n  function shipBadge(original){\n    const s = shipInfo(original);\n    const badge = '<span class=\"tp-badge\">'+(s.label||'')+'<\/span>';\n    return s.href ? ('<a href=\"'+s.href+'\" target=\"_blank\" rel=\"nofollow noopener\">'+badge+'<\/a>') : badge;\n  }\n\n  function renderTable(rows){\n    const th = {date:L.th_date,dep:L.th_dep,arr:L.th_arr,dur:L.th_dur,ship:L.th_ship,op:L.th_op};\n\n    const headCells = [\n      '<th>'+th.date+'<\/th>',\n      '<th>'+th.dep+'<\/th>',\n      '<th>'+th.arr+'<\/th>',\n      '<th>'+th.dur+'<\/th>'\n    ];\n    if (showShip) {\n      headCells.push('<th>'+th.ship+'<\/th>');\n    }\n    headCells.push('<th>'+th.op+'<\/th>');\n\n    let html = '<table class=\"tp-table\"><thead><tr>'+headCells.join('')+'<\/tr><\/thead><tbody>';\n\n    rows.forEach(r=>{\n      const dow = WD[r.dep.getDay()] || '';\n      const cells = [\n        '<td>'+dShort(r.dep)+' <span class=\"tp-day\">'+dow+'<\/span><\/td>',\n        '<td>'+hhmm(r.dep)+'<\/td>',\n        '<td>'+hhmm(r.arr)+'<\/td>',\n        '<td>'+dur(r.min)+'<\/td>'\n      ];\n      if (showShip) {\n        cells.push('<td>'+shipBadge(r.ship)+'<\/td>');\n      }\n      cells.push('<td>'+opCell(r)+'<\/td>');\n      html += '<tr>'+cells.join('')+'<\/tr>';\n    });\n    html += '<\/tbody><\/table>';\n    return html;\n  }\n\n  function renderCards(rows){\n    const routeName = cfg.label || '';\n    let html = '<div class=\"tp-cardlist\">';\n    rows.forEach(r=>{\n      const dow = WD[r.dep.getDay()] || '';\n      const times = hhmm(r.dep) + ' <span class=\"tp-arrow\">\u2192<\/span> ' + hhmm(r.arr);\n      html += '<div class=\"tp-card\">'+\n        \/\/ Row 1: Date + weekday + times\n        '<div class=\"tp-mrow\">'+\n          '<div class=\"lhs\"><span class=\"tp-sub\">'+dShort(r.dep)+'<\/span><span class=\"tp-day\">'+dow+'<\/span><\/div>'+\n          '<div class=\"rhs\"><span class=\"tp-time\">'+times+'<\/span><\/div>'+\n        '<\/div>';\n\n      if (showShip || routeName) {\n        html +=\n        \/\/ Row 2: Ship & Route (no prefixes)\n        '<div class=\"tp-mrow\">'+\n          '<div class=\"lhs\">'+(showShip ? shipBadge(r.ship) : '')+'<\/div>'+\n          (routeName ? ('<div class=\"rhs\">'+routeName+'<\/div>') : '<div class=\"rhs\"><\/div>')+\n        '<\/div>';\n      }\n\n      html +=\n        \/\/ Row 3: Operator (no book button)\n        '<div class=\"tp-mrow\">'+\n          '<div class=\"lhs\">'+opCell(r)+'<\/div>'+\n          '<div class=\"rhs\"><\/div>'+\n        '<\/div>'+\n      '<\/div>';\n    });\n    html += '<\/div>';\n    return html;\n  }\n\n  async function load(){\n    btn.disabled = true; setLoader(true); status.textContent = L.status_loading;\n    const start = new Date(dateEl.value || new Date());\n    const days  = Math.max(1, parseInt(daysEl.value,10)||1);\n    const end   = new Date(start); end.setDate(start.getDate()+days-1);\n    const url = new URL(cfg.rest); url.searchParams.set('route', String(cfg.route));\n    url.searchParams.set('from', iso(start)); url.searchParams.set('to', iso(end));\n    try{\n      const res = await fetch(url.toString(), {credentials:'same-origin'});\n      if(!res.ok) throw new Error('HTTP '+res.status);\n      const json = await res.json();\n      const list = (json && json.data && Array.isArray(json.data.rows)) ? json.data.rows : [];\n      const rows = list.map(t=>({\n        opId:t.operatorId||null, opName:t.operatorName||'', opLogo:t.operatorLogo||'', opLink:t.operatorLink||'',\n        dep:toLocal(t.departureTime), arr:toLocal(t.arrivalTime), min:t.durationInMinutes||0, ship:t.shipName||''\n      })).sort((a,b)=>a.dep-b.dep);\n      if(rows.length===0){ status.textContent = L.status_empty; out.innerHTML=''; return; }\n      status.textContent = (cfg.label? (cfg.label+': '):'') + (L.status_found||'%d sailings found').replace('%d', rows.length);\n      out.innerHTML = renderTable(rows) + renderCards(rows);\n    }catch(e){\n      status.textContent = 'Failed to load data'; out.innerHTML = '<pre>'+String(e.message||e)+'<\/pre>';\n    }finally{ setLoader(false); btn.disabled=false; }\n  }\n\n  btn.addEventListener('click', load);\n  if (cfg.autoload) load();\n})();\n<\/script>\n\n\n\n\n<h2 class=\"wp-block-heading\" id=\"tallinnhelsinki\">Aikataulu Tallinnasta Helsinkiin<\/h2>\n\n\n\n<p>T\u00e4m\u00e4 live-aikataulu listaa l\u00e4hd\u00f6t <a href=\"https:\/\/helsinki-tallinn.com\/fi\/tallinnan-satama\/\">Tallinnan satamasta<\/a> ja suunnitellut saapumiset Helsinkiin. Oletusn\u00e4kym\u00e4 n\u00e4ytt\u00e4\u00e4 <strong>seuraavat 3 p\u00e4iv\u00e4\u00e4<\/strong>, mutta p\u00e4iv\u00e4\u00e4 voi vaihtaa vapaasti. Nopeimmat alukset t\u00e4ll\u00e4 reitill\u00e4 ovat yleens\u00e4 <a href=\"https:\/\/helsinki-tallinn.com\/fi\/mystar\/\">MyStar<\/a> ja <a href=\"https:\/\/helsinki-tallinn.com\/fi\/megastar\/\">Megastar<\/a>.<\/p>\n\n\n<div class=\"tp-wrap\" data-config=\"{&quot;rest&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/fi\\\/wp-json\\\/timetables-pro\\\/v1\\\/timetables&quot;,&quot;route&quot;:162,&quot;days&quot;:3,&quot;autoload&quot;:true,&quot;label&quot;:&quot;Talinn - Helsinki&quot;,&quot;labels&quot;:{&quot;ui_date&quot;:&quot;Pvm&quot;,&quot;ui_days&quot;:&quot;P\\u00e4iv\\u00e4t&quot;,&quot;ui_button&quot;:&quot;N\\u00e4yt\\u00e4 l\\u00e4hd\\u00f6t&quot;,&quot;status_idle&quot;:&quot;Valitse pvm&quot;,&quot;status_loading&quot;:&quot;Lataa...&quot;,&quot;status_empty&quot;:&quot;Ei l\\u00e4ht\\u00f6j\\u00e4&quot;,&quot;status_found&quot;:&quot;%d l\\u00e4ht\\u00f6\\u00e4&quot;,&quot;th_date&quot;:&quot;Pvm&quot;,&quot;th_dep&quot;:&quot;L\\u00e4ht\\u00f6&quot;,&quot;th_arr&quot;:&quot;Saap.&quot;,&quot;th_dur&quot;:&quot;Kesto&quot;,&quot;th_ship&quot;:&quot;Laiva&quot;,&quot;th_op&quot;:&quot;Yhti\\u00f6&quot;,&quot;th_route&quot;:&quot;Reitti&quot;,&quot;wd_sun&quot;:&quot;Su&quot;,&quot;wd_mon&quot;:&quot;Ma&quot;,&quot;wd_tue&quot;:&quot;Ti&quot;,&quot;wd_wed&quot;:&quot;Ke&quot;,&quot;wd_thu&quot;:&quot;To&quot;,&quot;wd_fri&quot;:&quot;Pe&quot;,&quot;wd_sat&quot;:&quot;La&quot;,&quot;summary_footer_one&quot;:&quot;\\u00bb Kaikki %s l\\u00e4hd\\u00f6t&quot;,&quot;summary_footer_two&quot;:&quot;\\u00bb Lis\\u00e4\\u00e4 %1$s + %2$s&quot;,&quot;summary_footer_generic&quot;:&quot;Lis\\u00e4\\u00e4 l\\u00e4hd\\u00f6t&quot;,&quot;summary_header&quot;:&quot;Seuraavat 2 l\\u00e4ht\\u00f6\\u00e4:&quot;,&quot;summary_no_upcoming&quot;:&quot;Ei tulevia&quot;,&quot;more_link&quot;:&quot;Lis\\u00e4\\u00e4 l\\u00e4ht\\u00f6j\\u00e4&quot;},&quot;showLogo&quot;:true,&quot;shipMap&quot;:{&quot;fi&quot;:&quot;Finlandia&quot;},&quot;shipLinks&quot;:{&quot;fi&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/finlandia\\\/&quot;,&quot;viking xprs&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/viking-xprs\\\/&quot;,&quot;mystar&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/mystar\\\/&quot;,&quot;victoria i&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/victoria\\\/&quot;,&quot;megastar&quot;:&quot;https:\\\/\\\/helsinki-tallinn.com\\\/megastar\\\/&quot;},&quot;showShip&quot;:true}\">\n  <div class=\"tp-controls\" role=\"group\" aria-label=\"Timetable controls\">\n    <div>\n      <label for=\"tp-date\">Pvm<\/label><br>\n      <input id=\"tp-date\" class=\"tp-date\" type=\"date\" aria-label=\"Pvm\">\n    <\/div>\n    <div>\n      <label for=\"tp-range\">P\u00e4iv\u00e4t<\/label><br>\n      <select id=\"tp-range\" class=\"tp-range\" aria-label=\"P\u00e4iv\u00e4t\">\n        <option value=\"1\">1<\/option><option value=\"3\">3<\/option><option value=\"7\">7<\/option><option value=\"14\">14<\/option>\n      <\/select>\n    <\/div>\n    <div><button id=\"tp-load\" class=\"tp-btn\">N\u00e4yt\u00e4 l\u00e4hd\u00f6t<\/button><\/div>\n  <\/div>\n\n  <div id=\"tp-status\" class=\"tp-muted\" aria-live=\"polite\">Valitse pvm<\/div>\n  <div id=\"tp-results\"><\/div>\n\n  <div class=\"tp-loader\" aria-hidden=\"true\">\n    <div class=\"tp-loader-card\">\n      <div class=\"tp-spinner\" aria-hidden=\"true\"><\/div>\n      <div class=\"tp-loader-text\">Lataa...<\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n  const wrap   = document.currentScript.previousElementSibling;\n  const cfg    = JSON.parse(wrap.getAttribute('data-config')||'{}');\n  const L      = cfg.labels||{};\n  const dateEl = wrap.querySelector('#tp-date');\n  const daysEl = wrap.querySelector('#tp-range');\n  const btn    = wrap.querySelector('#tp-load');\n  const status = wrap.querySelector('#tp-status');\n  const out    = wrap.querySelector('#tp-results');\n  const loader = wrap.querySelector('.tp-loader');\n\n  const showShip = (cfg.showShip !== false && cfg.showShip !== 0 && cfg.showShip !== '0');\n\n  dateEl.valueAsDate = new Date();\n  Array.from(daysEl.options).forEach(o=>{ if(parseInt(o.value,10)===parseInt(cfg.days||7,10)) o.selected=true; });\n\n  function pad(n){ return String(n).padStart(2,'0'); }\n  function iso(d){ return d.getFullYear()+'-'+pad(d.getMonth()+1)+'-'+pad(d.getDate()); }\n  function toLocal(s){ return new Date(s); }\n  function hhmm(d){ return d.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}); }\n  function dShort(d){ return d.toLocaleDateString([], {day:'numeric', month:'short'}); } \/\/ short, no year\n  function dur(m){ const h=Math.floor(m\/60), r=m%60; return r? (h+'h '+r+'m') : (h+'h'); }\n\n  const WD = [\n    L.wd_sun || 'SUN',\n    L.wd_mon || 'MON',\n    L.wd_tue || 'TUE',\n    L.wd_wed || 'WED',\n    L.wd_thu || 'THU',\n    L.wd_fri || 'FRI',\n    L.wd_sat || 'SAT'\n  ];\n\n  function setLoader(v){ loader.classList.toggle('show', !!v); loader.setAttribute('aria-hidden', v?'false':'true'); }\n\n  function opCell(r){\n    const name = r.opName || ('Operator '+(r.opId||''));\n    const logo = (cfg.showLogo && r.opLogo) ? '<img decoding=\"async\" src=\"'+r.opLogo+'\" alt=\"'+name+'\"> ' : '';\n    const label = logo + '<span>'+name+'<\/span>';\n    return r.opLink ? '<a class=\"tp-op\" href=\"'+r.opLink+'\" target=\"_blank\" rel=\"nofollow noopener\">'+label+'<\/a>' : '<span class=\"tp-op\">'+label+'<\/span>';\n  }\n\n  function shipInfo(original){\n    const key = (original||'').toLowerCase().trim();\n    const label = (cfg.shipMap && cfg.shipMap[key]) || original || '';\n    const href  = (cfg.shipLinks && cfg.shipLinks[key]) || '';\n    return {label, href};\n  }\n\n  function shipBadge(original){\n    const s = shipInfo(original);\n    const badge = '<span class=\"tp-badge\">'+(s.label||'')+'<\/span>';\n    return s.href ? ('<a href=\"'+s.href+'\" target=\"_blank\" rel=\"nofollow noopener\">'+badge+'<\/a>') : badge;\n  }\n\n  function renderTable(rows){\n    const th = {date:L.th_date,dep:L.th_dep,arr:L.th_arr,dur:L.th_dur,ship:L.th_ship,op:L.th_op};\n\n    const headCells = [\n      '<th>'+th.date+'<\/th>',\n      '<th>'+th.dep+'<\/th>',\n      '<th>'+th.arr+'<\/th>',\n      '<th>'+th.dur+'<\/th>'\n    ];\n    if (showShip) {\n      headCells.push('<th>'+th.ship+'<\/th>');\n    }\n    headCells.push('<th>'+th.op+'<\/th>');\n\n    let html = '<table class=\"tp-table\"><thead><tr>'+headCells.join('')+'<\/tr><\/thead><tbody>';\n\n    rows.forEach(r=>{\n      const dow = WD[r.dep.getDay()] || '';\n      const cells = [\n        '<td>'+dShort(r.dep)+' <span class=\"tp-day\">'+dow+'<\/span><\/td>',\n        '<td>'+hhmm(r.dep)+'<\/td>',\n        '<td>'+hhmm(r.arr)+'<\/td>',\n        '<td>'+dur(r.min)+'<\/td>'\n      ];\n      if (showShip) {\n        cells.push('<td>'+shipBadge(r.ship)+'<\/td>');\n      }\n      cells.push('<td>'+opCell(r)+'<\/td>');\n      html += '<tr>'+cells.join('')+'<\/tr>';\n    });\n    html += '<\/tbody><\/table>';\n    return html;\n  }\n\n  function renderCards(rows){\n    const routeName = cfg.label || '';\n    let html = '<div class=\"tp-cardlist\">';\n    rows.forEach(r=>{\n      const dow = WD[r.dep.getDay()] || '';\n      const times = hhmm(r.dep) + ' <span class=\"tp-arrow\">\u2192<\/span> ' + hhmm(r.arr);\n      html += '<div class=\"tp-card\">'+\n        \/\/ Row 1: Date + weekday + times\n        '<div class=\"tp-mrow\">'+\n          '<div class=\"lhs\"><span class=\"tp-sub\">'+dShort(r.dep)+'<\/span><span class=\"tp-day\">'+dow+'<\/span><\/div>'+\n          '<div class=\"rhs\"><span class=\"tp-time\">'+times+'<\/span><\/div>'+\n        '<\/div>';\n\n      if (showShip || routeName) {\n        html +=\n        \/\/ Row 2: Ship & Route (no prefixes)\n        '<div class=\"tp-mrow\">'+\n          '<div class=\"lhs\">'+(showShip ? shipBadge(r.ship) : '')+'<\/div>'+\n          (routeName ? ('<div class=\"rhs\">'+routeName+'<\/div>') : '<div class=\"rhs\"><\/div>')+\n        '<\/div>';\n      }\n\n      html +=\n        \/\/ Row 3: Operator (no book button)\n        '<div class=\"tp-mrow\">'+\n          '<div class=\"lhs\">'+opCell(r)+'<\/div>'+\n          '<div class=\"rhs\"><\/div>'+\n        '<\/div>'+\n      '<\/div>';\n    });\n    html += '<\/div>';\n    return html;\n  }\n\n  async function load(){\n    btn.disabled = true; setLoader(true); status.textContent = L.status_loading;\n    const start = new Date(dateEl.value || new Date());\n    const days  = Math.max(1, parseInt(daysEl.value,10)||1);\n    const end   = new Date(start); end.setDate(start.getDate()+days-1);\n    const url = new URL(cfg.rest); url.searchParams.set('route', String(cfg.route));\n    url.searchParams.set('from', iso(start)); url.searchParams.set('to', iso(end));\n    try{\n      const res = await fetch(url.toString(), {credentials:'same-origin'});\n      if(!res.ok) throw new Error('HTTP '+res.status);\n      const json = await res.json();\n      const list = (json && json.data && Array.isArray(json.data.rows)) ? json.data.rows : [];\n      const rows = list.map(t=>({\n        opId:t.operatorId||null, opName:t.operatorName||'', opLogo:t.operatorLogo||'', opLink:t.operatorLink||'',\n        dep:toLocal(t.departureTime), arr:toLocal(t.arrivalTime), min:t.durationInMinutes||0, ship:t.shipName||''\n      })).sort((a,b)=>a.dep-b.dep);\n      if(rows.length===0){ status.textContent = L.status_empty; out.innerHTML=''; return; }\n      status.textContent = (cfg.label? (cfg.label+': '):'') + (L.status_found||'%d sailings found').replace('%d', rows.length);\n      out.innerHTML = renderTable(rows) + renderCards(rows);\n    }catch(e){\n      status.textContent = 'Failed to load data'; out.innerHTML = '<pre>'+String(e.message||e)+'<\/pre>';\n    }finally{ setLoader(false); btn.disabled=false; }\n  }\n\n  btn.addEventListener('click', load);\n  if (cfg.autoload) load();\n})();\n<\/script>\n\n\n\n\n<h2 class=\"wp-block-heading\">Tietoa live-aikataulusta<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n\t\n\t<li><strong>N\u00e4kym\u00e4 on live<\/strong> ja perustuu ajantasaiseen aikataulutietoon. Aikoihin voi tulla muutoksia s\u00e4\u00e4n, satamaliikenteen tai operatiivisten syiden takia.<\/li>\n\t\n\n\t\n\t<li>Joskus voit n\u00e4hd\u00e4 poikkeavan pitk\u00e4n keston (esimerkiksi <strong>13 tuntia<\/strong>). T\u00e4m\u00e4 tarkoittaa yleens\u00e4, ett\u00e4 sama alus tekee useamman osuuden tai odottaa satamassa, ja Helsinki-Tallinna n\u00e4kyy vain yhten\u00e4 osana pidemp\u00e4\u00e4 kokonaisuutta.<\/li>\n\t\n\n\t\n\t<li>Jos tulevia vuoroja puuttuu, syy on usein se, ett\u00e4 seuraavan kauden aikatauluja ei ole viel\u00e4 julkaistu tai niit\u00e4 p\u00e4ivitet\u00e4\u00e4n.<\/li>\n\t\n\n\t\n\t<li>Satamalle l\u00e4htemist\u00e4 varten tarkista my\u00f6s terminaalikohtaiset ohjeet: <a href=\"https:\/\/helsinki-tallinn.com\/fi\/helsingin-satama\/viking-line-terminaali\/\">Viking Line Helsinki<\/a>, <a href=\"https:\/\/helsinki-tallinn.com\/fi\/helsingin-satama\/eckero-line-terminaali\/\">Ecker\u00f6 Line Helsinki<\/a>, <a href=\"https:\/\/helsinki-tallinn.com\/fi\/helsingin-satama\/tallink-silja-terminaali\/\">Tallink Silja Helsinki<\/a> sek\u00e4 Tallinnan puolella <a href=\"https:\/\/helsinki-tallinn.com\/fi\/tallinnan-satama\/viking-line-terminaali\/\">Viking Line Tallinna<\/a>, <a href=\"https:\/\/helsinki-tallinn.com\/fi\/tallinnan-satama\/eckero-linen-terminaali\/\">Ecker\u00f6 Line Tallinna<\/a>, <a href=\"https:\/\/helsinki-tallinn.com\/fi\/tallinnan-satama\/tallink-silja-terminaali\/\">Tallink Silja Tallinna<\/a>.<\/li>\n\t\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Live-aikataulu n\u00e4ytt\u00e4\u00e4 oletuksena 3 p\u00e4iv\u00e4\u00e4, jotta lista pysyy selke\u00e4n\u00e4. Voit vaihtaa p\u00e4iv\u00e4m\u00e4\u00e4r\u00e4\u00e4 ja tarkistaa mink\u00e4 tahansa p\u00e4iv\u00e4n l\u00e4hd\u00f6t ja saapumiset. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":733,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"inline_featured_image":false,"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"class_list":["post-527","page","type-page","status-publish","has-post-thumbnail","hentry"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.2 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Tallinna Helsinki aikataulut - Helsinki-Tallinn.com Suomi<\/title>\n<meta name=\"description\" content=\"Tutustu Helsingin ja Tallinnan v\u00e4lisen risteyksen (Viking Line, Ecker\u00f6 Line ja Tallink) koko lauttaliikenteen aikatauluun t\u00e4st\u00e4.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/\" \/>\n<meta property=\"og:locale\" content=\"fi_FI\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Tallinna Helsinki aikataulut - Helsinki-Tallinn.com Suomi\" \/>\n<meta property=\"og:description\" content=\"Tutustu Helsingin ja Tallinnan v\u00e4lisen risteyksen (Viking Line, Ecker\u00f6 Line ja Tallink) koko lauttaliikenteen aikatauluun t\u00e4st\u00e4.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/\" \/>\n<meta property=\"og:site_name\" content=\"Helsinki-Tallinn.com Suomi\" \/>\n<meta property=\"article:modified_time\" content=\"2026-02-24T10:00:20+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/helsinki-tallinn.com\/fi\/wp-content\/uploads\/sites\/4\/2026\/02\/near-helsinki.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1920\" \/>\n\t<meta property=\"og:image:height\" content=\"500\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Arvioitu lukuaika\" \/>\n\t<meta name=\"twitter:data1\" content=\"2 minuuttia\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/\",\"url\":\"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/\",\"name\":\"Tallinna Helsinki aikataulut - Helsinki-Tallinn.com Suomi\",\"isPartOf\":{\"@id\":\"https:\/\/helsinki-tallinn.com\/fi\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/helsinki-tallinn.com\/fi\/wp-content\/uploads\/sites\/4\/2026\/02\/near-helsinki.jpg\",\"datePublished\":\"2024-06-20T13:02:54+00:00\",\"dateModified\":\"2026-02-24T10:00:20+00:00\",\"description\":\"Tutustu Helsingin ja Tallinnan v\u00e4lisen risteyksen (Viking Line, Ecker\u00f6 Line ja Tallink) koko lauttaliikenteen aikatauluun t\u00e4st\u00e4.\",\"breadcrumb\":{\"@id\":\"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/#breadcrumb\"},\"inLanguage\":\"fi\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"fi\",\"@id\":\"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/#primaryimage\",\"url\":\"https:\/\/helsinki-tallinn.com\/fi\/wp-content\/uploads\/sites\/4\/2026\/02\/near-helsinki.jpg\",\"contentUrl\":\"https:\/\/helsinki-tallinn.com\/fi\/wp-content\/uploads\/sites\/4\/2026\/02\/near-helsinki.jpg\",\"width\":1920,\"height\":500,\"caption\":\"Tallinna Helsinki aikataulut t\u00e4n\u00e4\u00e4n\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Helsinki Tallinna\",\"item\":\"https:\/\/helsinki-tallinn.com\/fi\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Tallinna Helsinki aikataulut\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/helsinki-tallinn.com\/fi\/#website\",\"url\":\"https:\/\/helsinki-tallinn.com\/fi\/\",\"name\":\"Helsinki-Tallinn.com Suomi\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/helsinki-tallinn.com\/fi\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/helsinki-tallinn.com\/fi\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"fi\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/helsinki-tallinn.com\/fi\/#organization\",\"name\":\"Helsinki-Tallinn.com Suomi\",\"url\":\"https:\/\/helsinki-tallinn.com\/fi\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"fi\",\"@id\":\"https:\/\/helsinki-tallinn.com\/fi\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/helsinki-tallinn.com\/fi\/wp-content\/uploads\/sites\/4\/2026\/02\/Helsinki-Talinn-powered.png\",\"contentUrl\":\"https:\/\/helsinki-tallinn.com\/fi\/wp-content\/uploads\/sites\/4\/2026\/02\/Helsinki-Talinn-powered.png\",\"width\":700,\"height\":200,\"caption\":\"Helsinki-Tallinn.com Suomi\"},\"image\":{\"@id\":\"https:\/\/helsinki-tallinn.com\/fi\/#\/schema\/logo\/image\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Tallinna Helsinki aikataulut - Helsinki-Tallinn.com Suomi","description":"Tutustu Helsingin ja Tallinnan v\u00e4lisen risteyksen (Viking Line, Ecker\u00f6 Line ja Tallink) koko lauttaliikenteen aikatauluun t\u00e4st\u00e4.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/","og_locale":"fi_FI","og_type":"article","og_title":"Tallinna Helsinki aikataulut - Helsinki-Tallinn.com Suomi","og_description":"Tutustu Helsingin ja Tallinnan v\u00e4lisen risteyksen (Viking Line, Ecker\u00f6 Line ja Tallink) koko lauttaliikenteen aikatauluun t\u00e4st\u00e4.","og_url":"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/","og_site_name":"Helsinki-Tallinn.com Suomi","article_modified_time":"2026-02-24T10:00:20+00:00","og_image":[{"width":1920,"height":500,"url":"https:\/\/helsinki-tallinn.com\/fi\/wp-content\/uploads\/sites\/4\/2026\/02\/near-helsinki.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_misc":{"Arvioitu lukuaika":"2 minuuttia"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/","url":"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/","name":"Tallinna Helsinki aikataulut - Helsinki-Tallinn.com Suomi","isPartOf":{"@id":"https:\/\/helsinki-tallinn.com\/fi\/#website"},"primaryImageOfPage":{"@id":"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/#primaryimage"},"image":{"@id":"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/#primaryimage"},"thumbnailUrl":"https:\/\/helsinki-tallinn.com\/fi\/wp-content\/uploads\/sites\/4\/2026\/02\/near-helsinki.jpg","datePublished":"2024-06-20T13:02:54+00:00","dateModified":"2026-02-24T10:00:20+00:00","description":"Tutustu Helsingin ja Tallinnan v\u00e4lisen risteyksen (Viking Line, Ecker\u00f6 Line ja Tallink) koko lauttaliikenteen aikatauluun t\u00e4st\u00e4.","breadcrumb":{"@id":"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/#breadcrumb"},"inLanguage":"fi","potentialAction":[{"@type":"ReadAction","target":["https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/"]}]},{"@type":"ImageObject","inLanguage":"fi","@id":"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/#primaryimage","url":"https:\/\/helsinki-tallinn.com\/fi\/wp-content\/uploads\/sites\/4\/2026\/02\/near-helsinki.jpg","contentUrl":"https:\/\/helsinki-tallinn.com\/fi\/wp-content\/uploads\/sites\/4\/2026\/02\/near-helsinki.jpg","width":1920,"height":500,"caption":"Tallinna Helsinki aikataulut t\u00e4n\u00e4\u00e4n"},{"@type":"BreadcrumbList","@id":"https:\/\/helsinki-tallinn.com\/fi\/aikataulu\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Helsinki Tallinna","item":"https:\/\/helsinki-tallinn.com\/fi\/"},{"@type":"ListItem","position":2,"name":"Tallinna Helsinki aikataulut"}]},{"@type":"WebSite","@id":"https:\/\/helsinki-tallinn.com\/fi\/#website","url":"https:\/\/helsinki-tallinn.com\/fi\/","name":"Helsinki-Tallinn.com Suomi","description":"","publisher":{"@id":"https:\/\/helsinki-tallinn.com\/fi\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/helsinki-tallinn.com\/fi\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"fi"},{"@type":"Organization","@id":"https:\/\/helsinki-tallinn.com\/fi\/#organization","name":"Helsinki-Tallinn.com Suomi","url":"https:\/\/helsinki-tallinn.com\/fi\/","logo":{"@type":"ImageObject","inLanguage":"fi","@id":"https:\/\/helsinki-tallinn.com\/fi\/#\/schema\/logo\/image\/","url":"https:\/\/helsinki-tallinn.com\/fi\/wp-content\/uploads\/sites\/4\/2026\/02\/Helsinki-Talinn-powered.png","contentUrl":"https:\/\/helsinki-tallinn.com\/fi\/wp-content\/uploads\/sites\/4\/2026\/02\/Helsinki-Talinn-powered.png","width":700,"height":200,"caption":"Helsinki-Tallinn.com Suomi"},"image":{"@id":"https:\/\/helsinki-tallinn.com\/fi\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/helsinki-tallinn.com\/fi\/wp-json\/wp\/v2\/pages\/527","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/helsinki-tallinn.com\/fi\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/helsinki-tallinn.com\/fi\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/helsinki-tallinn.com\/fi\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/helsinki-tallinn.com\/fi\/wp-json\/wp\/v2\/comments?post=527"}],"version-history":[{"count":7,"href":"https:\/\/helsinki-tallinn.com\/fi\/wp-json\/wp\/v2\/pages\/527\/revisions"}],"predecessor-version":[{"id":768,"href":"https:\/\/helsinki-tallinn.com\/fi\/wp-json\/wp\/v2\/pages\/527\/revisions\/768"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/helsinki-tallinn.com\/fi\/wp-json\/wp\/v2\/media\/733"}],"wp:attachment":[{"href":"https:\/\/helsinki-tallinn.com\/fi\/wp-json\/wp\/v2\/media?parent=527"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}