How to style (with CSS) "20201231123456" into "20201231-123456"?

Viewed 120

The "20201231123456" is a version identifier, to be displayed as static text in a page element. It's rather hard to parse visually, and I was wondering whether I could use pure CSS to display it split into its date and time components; that is, without needing to modify the actual value in the HTML source?

I've tried to search, but either I get results from 2011 saying it's not possible but someone's working on it, or results suggesting putting every character into its own span.

Is there a modern solution to this seemingly simple idea?

Bonus: To complicate matters, this goes into a long table with other stuff -- and some of the version identifiers are 5-digit numbers rather than date-times, but they are displayed in the same type of HTML element. Could the HTML element's styling accommodate both formats, so that "54321" remains as is, but "20201231123456" still becomes "20201231-123456"?

3 Answers

Here there is a CSS-only solution that works for both long (14 characters) and short values (less than 14 characters):

div {
  font-family: monospace, monospace;
  width: 15ch;
  overflow: hidden;
  letter-spacing: 20ch;
  text-shadow: 
    /* the date */
    -20ch 0, -40ch 0, -60ch 0, -80ch 0, -100ch 0, -120ch 0, -140ch 0,
    /* the time */
    -159ch 0, -179ch 0, -199ch 0, -219ch 0, -239ch 0, -259ch 0;
}
<div>20201231123456</div>

<div>12345</div>

The idea is using monospace font (same width all characters) and the ch unit that will allow you to move the characters around a little in an orderly fashion. Set a fixed width to the container, and then space them enough so they are out of the box. Finally, use text-shadow to project one character at a time.

I know, it may be tedious (and a bit silly), but it kind of works :)

But you wanted it with a dash, for that, use a pseudoelement like ::before or ::after:

div {
  font-family: monospace, monospace;
  width: 19ch;
  overflow: hidden;
  position: relative;
  letter-spacing: 20ch;
  text-shadow: 
    /* the date */
    -20ch 0, -40ch 0, -60ch 0, -80ch 0, -100ch 0, -120ch 0, -140ch 0,
    /* the time */
    -159ch 0, -179ch 0, -199ch 0, -219ch 0, -239ch 0, -259ch 0;
}

div::before {
  content: "-";
  position: absolute;
  left: 8ch;
}
<div>20201231123456</div>


And now for the cherry on top: use the pseudo-elements of the box to draw the lines and colons to format the date completely. Although then it won't work on smaller dates, just on long ones:

div {
  font-family: monospace, monospace;
  width: 19ch;
  overflow: hidden;
  position: relative;
  letter-spacing: 20ch;
  text-shadow: 
    /* the date */
    -20ch 0, -40ch 0, -60ch 0, -79ch 0, -99ch 0, -118ch 0, -138ch 0,
    /* the time */
    -157ch 0, -177ch 0, -196ch 0, -216ch 0, -235ch 0, -255ch 0;
}

div::before {
  content: "-:";
  position: absolute;
  text-shadow: 3ch 0, -12ch 0, -9ch 0;
  left: 4ch;
}
<div>20201231123456</div>

With CSS alone I don't think it's easily doable,
using JavaScript String/slice MDN it's quite simple:

const str = "20201231123456";
const [date, time] = [str.slice(0, 8), str.slice(8)];

console.log( `${date}-${time}` )

Or like:

const str = "20201231123456";
const res = /^(\d{8})(\d+)$/.exec(str).splice(1).join('-');

console.log(res)


Use-case example:

Say the version IDs are inside a specific element like <span> or <td> with a class .vid:

const prettyVID = el => {
  const str = el.textContent.trim();
  const [Y, M, D, h, m, s] = str.match(/^\d{4}|\d{2}/g);
  const ISO8601 = `${Y}-${M}-${D}T${h}:${m}:${s}Z`;
  const custom  = `${Y}${M}${D}-${h}${m}${s}`;

  el.title = new Date(ISO8601).toLocaleString('en', {timeZone: 'UTC'});
  el.textContent = custom;
}


document.querySelectorAll('.vid').forEach(prettyVID);
th, td {padding: 8px;}
<table>
  <thead>
    <tr><th>App name</th><th>Version (Hover to see date)</th></tr>
  </thead>
  <tbody>
    <tr><td>Lorem</td><td class="vid">20201231091015</td></tr>
    <tr><td>Ipsum</td><td class="vid">20200224123456</td></tr>
  </tbody>
</table>

The difficulty here is that CSS is not the right technology for this task.

CSS has many presentational capabilities, but... the smallest atomic particle that you can style in CSS is an HTML element like <div> or <span> or <strong> etc.

If, for example, we take the following <div> element:

<div>target text here</div>

we can use CSS to present the <div> in any number of ways.

And if we mark it up as:

<div><span>target</span> <span>text</span> <span>here</span></div>

then we can style any or all of the <span> elements.

But we can't style any part of the markup that is smaller than an element.


N.B. We could argue there are a limited number of (quite specific) exceptions to the final statement immediately above, including the CSS pseudo-elements ::first-letter and ::first-line.


What approach can we adopt if we can't use CSS?

As @BastianSpringer notes in the comments above, Javascript has the capability to produce the effect you want to achieve.

Working Example:

let numbers = [...document.getElementsByClassName('number')];

for (let number of numbers) {

  numberText = number.textContent;

  if (numberText.length > 8) {
    let numberArray = numberText.split('');
    numberArray.splice(8, 0, '-');
    numberText = numberArray.join('');
    number.textContent = numberText;
  }
}
<p class="number">54321</p>
<p class="number">20201231123456</p>

Related