locales – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Mon, 13 Mar 2023 13:24:00 +0000 en-US hourly 1 https://wordpress.org/?v=6.2.2 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 locales – CSS-Tricks https://css-tricks.com 32 32 45537868 Making Calendars With Accessibility and Internationalization in Mind https://css-tricks.com/making-calendars-with-accessibility-and-internationalization-in-mind/ https://css-tricks.com/making-calendars-with-accessibility-and-internationalization-in-mind/#comments Mon, 13 Mar 2023 13:23:52 +0000 https://css-tricks.com/?p=376950 Doing a quick search here on CSS-Tricks shows just how many different ways there are to approach calendars. Some show how CSS Grid can create the layout efficiently. Some attempt to bring actual data into the mix. Some …


Making Calendars With Accessibility and Internationalization in Mind originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Doing a quick search here on CSS-Tricks shows just how many different ways there are to approach calendars. Some show how CSS Grid can create the layout efficiently. Some attempt to bring actual data into the mix. Some rely on a framework to help with state management.

There are many considerations when building a calendar component — far more than what is covered in the articles I linked up. If you think about it, calendars are fraught with nuance, from handling timezones and date formats to localization and even making sure dates flow from one month to the next… and that’s before we even get into accessibility and additional layout considerations depending on where the calendar is displayed and whatnot.

Many developers fear the Date() object and stick with older libraries like moment.js. But while there are many “gotchas” when it comes to dates and formatting, JavaScript has a lot of cool APIs and stuff to help out!

January 2023 calendar grid.

I don’t want to re-create the wheel here, but I will show you how we can get a dang good calendar with vanilla JavaScript. We’ll look into accessibility, using semantic markup and screenreader-friendly <time> -tags — as well as internationalization and formatting, using the Intl.Locale, Intl.DateTimeFormat and Intl.NumberFormat-APIs.

In other words, we’re making a calendar… only without the extra dependencies you might typically see used in a tutorial like this, and with some of the nuances you might not typically see. And, in the process, I hope you’ll gain a new appreciation for newer things that JavaScript can do while getting an idea of the sorts of things that cross my mind when I’m putting something like this together.

First off, naming

What should we call our calendar component? In my native language, it would be called “kalender element”, so let’s use that and shorten that to “Kal-El” — also known as Superman’s name on the planet Krypton.

Let’s create a function to get things going:

function kalEl(settings = {}) { ... }

This method will render a single month. Later we’ll call this method from [...Array(12).keys()] to render an entire year.

Initial data and internationalization

One of the common things a typical online calendar does is highlight the current date. So let’s create a reference for that:

const today = new Date();

Next, we’ll create a “configuration object” that we’ll merge with the optional settings object of the primary method:

const config = Object.assign(
  {
    locale: (document.documentElement.getAttribute('lang') || 'en-US'), 
    today: { 
      day: today.getDate(),
      month: today.getMonth(),
      year: today.getFullYear() 
    } 
  }, settings
);

We check, if the root element (<html>) contains a lang-attribute with locale info; otherwise, we’ll fallback to using en-US. This is the first step toward internationalizing the calendar.

We also need to determine which month to initially display when the calendar is rendered. That’s why we extended the config object with the primary date. This way, if no date is provided in the settings object, we’ll use the today reference instead:

const date = config.date ? new Date(config.date) : today;

We need a little more info to properly format the calendar based on locale. For example, we might not know whether the first day of the week is Sunday or Monday, depending on the locale. If we have the info, great! But if not, we’ll update it using the Intl.Locale API. The API has a weekInfo object that returns a firstDay property that gives us exactly what we’re looking for without any hassle. We can also get which days of the week are assigned to the weekend:

if (!config.info) config.info = new Intl.Locale(config.locale).weekInfo || { 
  firstDay: 7,
  weekend: [6, 7] 
};

Again, we create fallbacks. The “first day” of the week for en-US is Sunday, so it defaults to a value of 7. This is a little confusing, as the getDay method in JavaScript returns the days as [0-6], where 0 is Sunday… don’t ask me why. The weekends are Saturday and Sunday, hence [6, 7].

Before we had the Intl.Locale API and its weekInfo method, it was pretty hard to create an international calendar without many **objects and arrays with information about each locale or region. Nowadays, it’s easy-peasy. If we pass in en-GB, the method returns:

// en-GB
{
  firstDay: 1,
  weekend: [6, 7],
  minimalDays: 4
}

In a country like Brunei (ms-BN), the weekend is Friday and Sunday:

// ms-BN
{
  firstDay: 7,
  weekend: [5, 7],
  minimalDays: 1
}

You might wonder what that minimalDays property is. That’s the fewest days required in the first week of a month to be counted as a full week. In some regions, it might be just one day. For others, it might be a full seven days.

Next, we’ll create a render method within our kalEl-method:

const render = (date, locale) => { ... }

We still need some more data to work with before we render anything:

const month = date.getMonth();
const year = date.getFullYear();
const numOfDays = new Date(year, month + 1, 0).getDate();
const renderToday = (year === config.today.year) && (month === config.today.month);

The last one is a Boolean that checks whether today exists in the month we’re about to render.

Semantic markup

We’re going to get deeper in rendering in just a moment. But first, I want to make sure that the details we set up have semantic HTML tags associated with them. Setting that up right out of the box gives us accessibility benefits from the start.

Calendar wrapper

First, we have the non-semantic wrapper: <kal-el>. That’s fine because there isn’t a semantic <calendar> tag or anything like that. If we weren’t making a custom element, <article> might be the most appropriate element since the calendar could stand on its own page.

Month names

The <time> element is going to be a big one for us because it helps translate dates into a format that screenreaders and search engines can parse more accurately and consistently. For example, here’s how we can convey “January 2023” in our markup:

<time datetime="2023-01">January <i>2023</i></time>

Day names

The row above the calendar’s dates containing the names of the days of the week can be tricky. It’s ideal if we can write out the full names for each day — e.g. Sunday, Monday, Tuesday, etc. — but that can take up a lot of space. So, let’s abbreviate the names for now inside of an <ol> where each day is a <li>:

<ol>
  <li><abbr title="Sunday">Sun</abbr></li>
  <li><abbr title="Monday">Mon</abbr></li>
  <!-- etc. -->
</ol>

We could get tricky with CSS to get the best of both worlds. For example, if we modified the markup a bit like this:

<ol>
  <li>
    <abbr title="S">Sunday</abbr>
  </li>
</ol>

…we get the full names by default. We can then “hide” the full name when space runs out and display the title attribute instead:

@media all and (max-width: 800px) {
  li abbr::after {
    content: attr(title);
  }
}

But, we’re not going that way because the Intl.DateTimeFormat API can help here as well. We’ll get to that in the next section when we cover rendering.

Day numbers

Each date in the calendar grid gets a number. Each number is a list item (<li>) in an ordered list (<ol>), and the inline <time> tag wraps the actual number.

<li>
  <time datetime="2023-01-01">1</time>
</li>

And while I’m not planning to do any styling just yet, I know I will want some way to style the date numbers. That’s possible as-is, but I also want to be able to style weekday numbers differently than weekend numbers if I need to. So, I’m going to include data-* attributes specifically for that: data-weekend and data-today.

Week numbers

There are 52 weeks in a year, sometimes 53. While it’s not super common, it can be nice to display the number for a given week in the calendar for additional context. I like having it now, even if I don’t wind up not using it. But we’ll totally use it in this tutorial.

We’ll use a data-weeknumber attribute as a styling hook and include it in the markup for each date that is the week’s first date.

<li data-day="7" data-weeknumber="1" data-weekend="">
  <time datetime="2023-01-08">8</time>
</li>

Rendering

Let’s get the calendar on a page! We already know that <kal-el> is the name of our custom element. First thing we need to configure it is to set the firstDay property on it, so the calendar knows whether Sunday or some other day is the first day of the week.

<kal-el data-firstday="${ config.info.firstDay }">

We’ll be using template literals to render the markup. To format the dates for an international audience, we’ll use the Intl.DateTimeFormat API, again using the locale we specified earlier.

The month and year

When we call the month, we can set whether we want to use the long name (e.g. February) or the short name (e.g. Feb.). Let’s use the long name since it’s the title above the calendar:

<time datetime="${year}-${(pad(month))}">
  ${new Intl.DateTimeFormat(
    locale,
    { month:'long'}).format(date)} <i>${year}</i>
</time>

Weekday names

For weekdays displayed above the grid of dates, we need both the long (e.g. “Sunday”) and short (abbreviated, ie. “Sun”) names. This way, we can use the “short” name when the calendar is short on space:

Intl.DateTimeFormat([locale], { weekday: 'long' })
Intl.DateTimeFormat([locale], { weekday: 'short' })

Let’s make a small helper method that makes it a little easier to call each one:

const weekdays = (firstDay, locale) => {
  const date = new Date(0);
  const arr = [...Array(7).keys()].map(i => {
    date.setDate(5 + i)
    return {
      long: new Intl.DateTimeFormat([locale], { weekday: 'long'}).format(date),
      short: new Intl.DateTimeFormat([locale], { weekday: 'short'}).format(date)
    }
  })
  for (let i = 0; i < 8 - firstDay; i++) arr.splice(0, 0, arr.pop());
  return arr;
}

Here’s how we invoke that in the template:

<ol>
  ${weekdays(config.info.firstDay,locale).map(name => `
    <li>
      <abbr title="${name.long}">${name.short}</abbr>
    </li>`).join('')
  }
</ol>

Day numbers

And finally, the days, wrapped in an <ol> element:

${[...Array(numOfDays).keys()].map(i => {
  const cur = new Date(year, month, i + 1);
  let day = cur.getDay(); if (day === 0) day = 7;
  const today = renderToday && (config.today.day === i + 1) ? ' data-today':'';
  return `
    <li data-day="${day}"${today}${i === 0 || day === config.info.firstDay ? ` data-weeknumber="${new Intl.NumberFormat(locale).format(getWeek(cur))}"`:''}${config.info.weekend.includes(day) ? ` data-weekend`:''}>
      <time datetime="${year}-${(pad(month))}-${pad(i)}" tabindex="0">
        ${new Intl.NumberFormat(locale).format(i + 1)}
      </time>
    </li>`
}).join('')}

Let’s break that down:

  1. We create a “dummy” array, based on the “number of days” variable, which we’ll use to iterate.
  2. We create a day variable for the current day in the iteration.
  3. We fix the discrepancy between the Intl.Locale API and getDay().
  4. If the day is equal to today, we add a data-* attribute.
  5. Finally, we return the <li> element as a string with merged data.
  6. tabindex="0" makes the element focusable, when using keyboard navigation, after any positive tabindex values (Note: you should never add positive tabindex-values)

To “pad” the numbers in the datetime attribute, we use a little helper method:

const pad = (val) => (val + 1).toString().padStart(2, '0');

Week number

Again, the “week number” is where a week falls in a 52-week calendar. We use a little helper method for that as well:

function getWeek(cur) {
  const date = new Date(cur.getTime());
  date.setHours(0, 0, 0, 0);
  date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
  const week = new Date(date.getFullYear(), 0, 4);
  return 1 + Math.round(((date.getTime() - week.getTime()) / 86400000 - 3 + (week.getDay() + 6) % 7) / 7);
}

I didn’t write this getWeek-method. It’s a cleaned up version of this script.

And that’s it! Thanks to the Intl.Locale, Intl.DateTimeFormat and Intl.NumberFormat APIs, we can now simply change the lang-attribute of the <html> element to change the context of the calendar based on the current region:

January 2023 calendar grid.
de-DE
January 2023 calendar grid.
fa-IR
January 2023 calendar grid.
zh-Hans-CN-u-nu-hanidec

Styling the calendar

You might recall how all the days are just one <ol> with list items. To style these into a readable calendar, we dive into the wonderful world of CSS Grid. In fact, we can repurpose the same grid from a starter calendar template right here on CSS-Tricks, but updated a smidge with the :is() relational pseudo to optimize the code.

Notice that I’m defining configurable CSS variables along the way (and prefixing them with ---kalel- to avoid conflicts).

kal-el :is(ol, ul) {
  display: grid;
  font-size: var(--kalel-fz, small);
  grid-row-gap: var(--kalel-row-gap, .33em);
  grid-template-columns: var(--kalel-gtc, repeat(7, 1fr));
  list-style: none;
  margin: unset;
  padding: unset;
  position: relative;
}
Seven-column calendar grid with grid lines shown.

Let’s draw borders around the date numbers to help separate them visually:

kal-el :is(ol, ul) li {
  border-color: var(--kalel-li-bdc, hsl(0, 0%, 80%));
  border-style: var(--kalel-li-bds, solid);
  border-width: var(--kalel-li-bdw, 0 0 1px 0);
  grid-column: var(--kalel-li-gc, initial);
  text-align: var(--kalel-li-tal, end); 
}

The seven-column grid works fine when the first day of the month is also the first day of the week for the selected locale). But that’s the exception rather than the rule. Most times, we’ll need to shift the first day of the month to a different weekday.

Showing the first day of the month falling on a Thursday.

Remember all the extra data-* attributes we defined when writing our markup? We can hook into those to update which grid column (--kalel-li-gc) the first date number of the month is placed on:

[data-firstday="1"] [data-day="3"]:first-child {
  --kalel-li-gc: 1 / 4;
}

In this case, we’re spanning from the first grid column to the fourth grid column — which will automatically “push” the next item (Day 2) to the fifth grid column, and so forth.

Let’s add a little style to the “current” date, so it stands out. These are just my styles. You can totally do what you’d like here.

[data-today] {
  --kalel-day-bdrs: 50%;
  --kalel-day-bg: hsl(0, 86%, 40%);
  --kalel-day-hover-bgc: hsl(0, 86%, 70%);
  --kalel-day-c: #fff;
}

I like the idea of styling the date numbers for weekends differently than weekdays. I’m going to use a reddish color to style those. Note that we can reach for the :not() pseudo-class to select them while leaving the current date alone:

[data-weekend]:not([data-today]) { 
  --kalel-day-c: var(--kalel-weekend-c, hsl(0, 86%, 46%));
}

Oh, and let’s not forget the week numbers that go before the first date number of each week. We used a data-weeknumber attribute in the markup for that, but the numbers won’t actually display unless we reveal them with CSS, which we can do on the ::before pseudo-element:

[data-weeknumber]::before {
  display: var(--kalel-weeknumber-d, inline-block);
  content: attr(data-weeknumber);
  position: absolute;
  inset-inline-start: 0;
  /* additional styles */
}

We’re technically done at this point! We can render a calendar grid that shows the dates for the current month, complete with considerations for localizing the data by locale, and ensuring that the calendar uses proper semantics. And all we used was vanilla JavaScript and CSS!

But let’s take this one more step

Rendering an entire year

Maybe you need to display a full year of dates! So, rather than render the current month, you might want to display all of the month grids for the current year.

Well, the nice thing about the approach we’re using is that we can call the render method as many times as we want and merely change the integer that identifies the month on each instance. Let’s call it 12 times based on the current year.

as simple as calling the render-method 12 times, and just change the integer for monthi:

[...Array(12).keys()].map(i =>
  render(
    new Date(date.getFullYear(),
    i,
    date.getDate()),
    config.locale,
    date.getMonth()
  )
).join('')

It’s probably a good idea to create a new parent wrapper for the rendered year. Each calendar grid is a <kal-el> element. Let’s call the new parent wrapper <jor-el>, where Jor-El is the name of Kal-El’s father.

<jor-el id="app" data-year="true">
  <kal-el data-firstday="7">
    <!-- etc. -->
  </kal-el>

  <!-- other months -->
</jor-el>

We can use <jor-el> to create a grid for our grids. So meta!

jor-el {
  background: var(--jorel-bg, none);
  display: var(--jorel-d, grid);
  gap: var(--jorel-gap, 2.5rem);
  grid-template-columns: var(--jorel-gtc, repeat(auto-fill, minmax(320px, 1fr)));
  padding: var(--jorel-p, 0);
}

Final demo

Bonus: Confetti Calendar

I read an excellent book called Making and Breaking the Grid the other day and stumbled on this beautiful “New Year’s poster”:

Source: Making and Breaking the Grid (2nd Edition) by Timothy Samara

I figured we could do something similar without changing anything in the HTML or JavaScript. I’ve taken the liberty to include full names for months, and numbers instead of day names, to make it more readable. Enjoy!


Making Calendars With Accessibility and Internationalization in Mind originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/making-calendars-with-accessibility-and-internationalization-in-mind/feed/ 1 376950
PHP Date and Time Recipes https://css-tricks.com/php-date-and-time-recipes/ https://css-tricks.com/php-date-and-time-recipes/#comments Thu, 18 Nov 2021 15:38:51 +0000 https://css-tricks.com/?p=356570 Dealing with dates and times is one of those things that can frustrate programmers a lot. At the same time, they are fundamental to software development, used from everything from meta and how things are ordered to time-based triggers and …


PHP Date and Time Recipes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>

Dealing with dates and times is one of those things that can frustrate programmers a lot. At the same time, they are fundamental to software development, used from everything from meta and how things are ordered to time-based triggers and lots in between.

Dates and times are prone to errors too. Handle them incorrectly, and they can confuse end-users and fellow programmers alike.

This is a quick guide to dealing with dates and times specifically in the PHP programming language. It’s meant to be a reference to the most common needs you’ll have, like formatting and adjusting dates. It’s simple, but it’s likely going to cover 80% of your needs.

Table of Contents

This research is brought to you by support from Frontend Masters, CSS-Tricks’ official learning partner.

Need front-end development training?

Frontend Masters is the best place to get it. They have courses on all the most important front-end technologies. Interested in going full-stack? Here’s your best bet:


Get the current date and time

One thing to know is that the dates and times can be represented in three forms: a timestamp (i.e. epoch time), a DateTime object, and a string.

First up, a recipe to get the current date and time:

<?php

$now = new DateTime();
var_dump($now);

// object(DateTime)#1 (3) {
//   ["date"]=>
//   string(26) "2021-10-13 22:25:11.790490"
//   ["timezone_type"]=>
//   int(3)
//   ["timezone"]=>
//   string(12) "Asia/Jakarta"
// }

This provides a DateTime object that can be used to create a date and time string:

<?php

$now = new DateTime();
echo $now->format("Y-m-d"); // 2021-10-13
echo $now->format("Y-m-d h:i:s A"); // 2021-10-13 10:10:31 PM

Intuitively, you know that Y refers to the year, m refers to the month, d refers to the day of the month, and so on. The full list of the parameters can be found in the PHP manual, but I’ll drop some of the most common ones here for reference.

Day of the month
dDay of the month. two digits with leading zeros01 – 31
jDay of the month without leading zeros1 – 31
SIncludes the English suffix.stndrdth (e.g. 1st2nd3rd4th)
Weekday
DAbbreviated textual representation of a day, in three lettersSun – Sat
lA full textual representation of a weekday.Sunday – Saturday
Month
FA full textual representation of a month, such as January or MarchJanuary – December
MAbbreviated textual representation of a month, in three lettersJan – Dec
mNumeric representation of a month, with leading zeros01 – 12
nNumeric representation of a month, without leading zeros1 – 12
Year
YA full numeric representation of a year, 4 digitsE.g. 1999 or 2003
yA two digit representation of a yearE.g. 99 or 03
Time
AUppercase Ante Meridiem and Post MeridiemAM or PM
g12-hour format of an hour without leading zeros1 – 12
h12-hour format of an hour with leading zeros01 – 12
iMinutes with leading zeros00 – 59
sSeconds with leading zeros00 – 59

The DateTime object can be converted to a timestamp:

<?php

$now = new DateTime();
echo $now->getTimestamp(); // 1634139081

But we can also get the current time in timestamp without constructing a DateTime object:

<?php

echo time(); // 1634139081

Construct a DateTime object of a specific time

What if we want to construct a DateTime for a particular time, like July 14th, 2011? We can pass a formatted string date to the constructor:

<?php

$date = new DateTime("2011-07-14");
var_dump($date);

// object(DateTime)#1 (3) {
//   ["date"]=>
//   string(26) "2011-07-14 00:00:00.000000"
//   ["timezone_type"]=>
//   int(3)
//   ["timezone"]=>
//   string(12) "Asia/Jakarta"
// }

The constructor accepts other formats as well:

<?php

$date = new DateTime("14-07-2011");
var_dump($date);

// object(DateTime)#1 (3) {
//   ["date"]=>
//   string(26) "2011-07-14 00:00:00.000000"
//   ["timezone_type"]=>
//   int(3)
//   ["timezone"]=>
//   string(12) "Asia/Jakarta"
// }

But be careful with an ambiguous format, like this:

<?php

$date = new DateTime("07/14/2011");
var_dump($date);

// object(DateTime)#1 (3) {
//   ["date"]=>
//   string(26) "2011-07-14 00:00:00.000000"
//   ["timezone_type"]=>
//   int(3)
//   ["timezone"]=>
//   string(12) "Asia/Jakarta"
// }

You might think that everyone should be familiar with an American date format. But not everyone is and it might be interpreted differently. Not PostgreSQL.

CREATE TABLE IF NOT EXISTS public.datetime_demo
(
  created_at date
);

insert into datetime_demo (created_at) values ('07/12/2011');

select created_at from datetime_demo; /* 2011-12-07 */

You may have thought that would return July 12th, 2011, but it was December 7th, 2011, instead. A better way is to use an explicit format:

<?php

$date = DateTime::createFromFormat('m/d/y', "10/08/21");
var_dump($date);

//object(DateTime)#2 (3) {
//  ["date"]=>
//  string(26) "2021-10-08 16:00:47.000000"
//  ["timezone_type"]=>
//  int(3)
//  ["timezone"]=>
//  string(12) "Asia/Jakarta"
//}

What if we want to construct a DateTime object from a timestamp?

<?php

$date = new DateTime();
$date->setTimestamp(1634142890);
var_dump($date);

//object(DateTime)#1 (3) {
//  ["date"]=>
//  string(26) "2021-10-13 23:34:50.000000"
//  ["timezone_type"]=>
//  int(3)
//  ["timezone"]=>
//  string(12) "Asia/Jakarta"
// }

We don’t have to create a DateTime object if we want to convert a timestamp object to a formatted date string:

<?php

echo date("Y-m-d h:i A", time()); // 2021-10-14 04:10 PM

Timezones

We can create a DateTime object that includes timezone information, like if we’re dealing with Pacific Standard Time, Eastern Daylight Time, etc.

<?php

$timezone = new DateTimeZone("America/New_York");
$date = new DateTime("2021-10-13 05:00", $timezone);
var_dump($date);

// object(DateTime)#1 (3) {
//   ["date"]=>
//   string(26) "2021-10-13 05:00:00.000000"
//   ["timezone_type"]=>
//   int(3)
//   ["timezone"]=>
//   string(16) "America/New_York"
// }

// Eastern Daylight Time, for example: New York
$date = new DateTime("2021-10-13 05:00 EDT");
var_dump($date);

// object(DateTime)#2 (3) {
//   ["date"]=>
//   string(26) "2021-10-13 05:00:00.000000"
//   ["timezone_type"]=>
//   int(2)
//   ["timezone"]=>
//   string(3) "EDT"
// }

$date = new DateTime("2021-10-13 05:00 -04:00");
var_dump($date);

// object(DateTime)#1 (3) {
//   ["date"]=>
//   string(26) "2021-10-13 05:00:00.000000"
//   ["timezone_type"]=>
//   int(1)
//   ["timezone"]=>
//   string(6) "-04:00"
// }

There are three ways to create a DateTime object with timezone information. The timezone_type accepts different values for each one.

But say we want to convert a date and time that’s displayed in New York’s timezone to display Jakarta’s timezone instead?

<?php

$newYorkTimeZone = new DateTimeZone("America/New_York");
$date = new DateTime("2021-11-11 05:00", $newYorkTimeZone);
echo $date->format("Y-m-d h:i A"); // 2021-11-11 05:00 AM
$jakartaTimeZone = new DateTimeZone("Asia/Jakarta");
$date->setTimeZone($jakartaTimeZone);
echo $date->format("Y-m-d h:i A"); // 2021-11-11 05:00 PM

When it’s 05:00 AM in New York, it’s 05:00 PM in Jakarta on the same day. Jakarta is 12 hours ahead of New York on November 11th 2021. But one month earlier, Jakarta is only 11 hours ahead of New York as shown below:

<?php

$newYorkTimeZone = new DateTimeZone("America/New_York");
$date = new DateTime("2021-10-11 05:00", $newYorkTimeZone);
echo $date->format("Y-m-d h:i A"); // 2021-10-11 05:00 AM
$jakartaTimeZone = new DateTimeZone("Asia/Jakarta");
$date->setTimeZone($jakartaTimeZone);
echo $date->format("Y-m-d h:i A"); // 2021-10-11 04:00 PM

PHP handles Daylight Saving Time for you automatically.

Localization

This is a common way to display date and time in the United States:

<?php

$now = new DateTime();
echo $now->format("m/d/Y h:i A"); // 10/14/2021 03:00 PM

But someone in France might prefer something more common to their locale. C’est horrible, they’d complain. For one, nobody puts the month before month day, except the U.S. Second, France doesn’t use AM or PM — they use the 24-hour format (e.g. 14:00 instead of 2:00 PM) like the military. This is how you make a French local happy.

<?php

$now = new DateTime();
echo $now->format("d/m/Y H:i"); // 14/10/2021 15:00

But this requires an intimate knowledge about a specific country or area. Instead, we can localize the date. To localize a date, we need to install the internationalization support for PHP. In Ubuntu, we can do this step:

$ sudo apt-get install php-intl

To display a date and time in French, we can use IntlDateFormatter:

$locale = "fr_FR.UTF-8";
$formatter = new IntlDateFormatter($locale, IntlDateFormatter::FULL, IntlDateFormatter::SHORT, "Asia/Singapore");
$date = new DateTime("2020-10-10 00:00 UTC");
echo $formatter->format($date); // samedi 10 octobre 2020 à 08:00

You pass the French locale as the first parameter of IntlDateFormatter.
The second parameter is the format for the date. The third parameter is the format for the time. The timezone for displaying the date and time is in the fourth parameter.

Besides IntlDateFormatter::FULL and IntlDateFormatter::SHORT, other popular formats are IntlDateFormatter::NONE, IntlDateFormatter::LONG,
and IntlDateFormatter::MEDIUM.

If you use IntlDateFormatter::NONE for the time or in the third parameter, it means you don’t include the time in the format:

$locale = "fr_FR.UTF-8";
$formatter = new IntlDateFormatter($locale, IntlDateFormatter::LONG, IntlDateFormatter::NONE, "Asia/Singapore");
$date = new DateTime("2020-10-10 00:00 UTC");
echo $formatter->format($date); // 10 octobre 2020

Time travel

Let’s do time travel to the past and the future. First, let’s get acquainted with DateInterval:

<?php

$interval = new DateInterval("P4M1W2DT2H5M");

// P 4M 1W 2D T 2H 5M
//
// P = Period interval (years, months, weeks, days)
// 4M = 4 months
// 1W = 1 week
// 2D = 2 days
//
// T = Time interval (hours, minutes, seconds)
// 2H = 2 hours
// 5M = 5 minutes

The P and T are to separate period interval and time interval. Here’s how we can travel to the future:

<?php

$date = new DateTime("2021-10-14");
$interval = new DateInterval("P2D"); // 2 days
$futureDate = $date->add($interval);
echo $futureDate->format("Y-m-d"); // 2021-10-16

And here’s how we go back in time:

<?php

$date = new DateTime("2021-10-14 10:00");
$interval = new DateInterval("PT6H"); // 6 hours
$pastDate = $date->sub($interval);
echo $pastDate->format("Y-m-d H:i"); // 2021-10-14 04:00

If we want to time travel with the name of the weekday, we can combine the strtotime() function and the setTimestamp() method of a DateTime object:

<?php

$nextTuesday = strtotime("next tuesday");
$date = new DateTime("2021-10-14");
$date->setTimestamp($nextTuesday);
echo $date->format("Y-m-d"); // 2021-10-19

See the full list of strtotime() parameters in the PHP docs.

Recurring dates and times

It’s a common feature in calendar apps to set a reminder that repeats every so often, like every two days or every week. We can use DatePeriod to represent a period of time:

<?php

$start = new DateTime("2021-10-01");
$end = new DateTime("2021-11-01");
$interval = new DateInterval("P1W"); // 1 week
$range = new DatePeriod($start, $interval, $end);

// Starting from October 1st 2021 (inclusive), jump every 1 week
// until November 1st 2021 (exclusive)
foreach ($range as $date) {
  echo $date->format("Y-m-d") . "n";
}

// 2022-10-01
// 2022-10-08
// 2022-10-15
// 2022-10-22
// 2022-10-29

How many days ago?

You know how services like Twitter will show that someone posted X number of minutes/hours/days/etc. ago? We can do the same thing by calculating how much time has elapsed between the the current time and when that action occurred.

<?php

$date = new DateTime("2022-10-30");

$date2 = new DateTime("2022-10-25");
$date3 = new DateTime("2022-10-10");
$date4 = new DateTime("2022-03-30");
$date5 = new DateTime("2020-03-30");

function get_period_ago($endDate, $startDate) {
  $dateInterval = $endDate->diff($startDate);

  if ($dateInterval->invert==1) {
    if ($dateInterval->y > 0) {
      return $dateInterval->y . " years agon";
    } if ($dateInterval->m > 0) {
      return $dateInterval->m . " months agon";
    } if ($dateInterval->d > 7) {
      return (int)($dateInterval->d / 7) . " weeks agon";
    } if ($dateInterval->d > 0) {
      return $dateInterval->d . " days agon";
    }
  }
}

echo get_period_ago($date, $date2); // 5 days ago
echo get_period_ago($date, $date3); // 2 weeks ago
echo get_period_ago($date, $date4); // 7 months ago
echo get_period_ago($date, $date5); // 2 years ago

After getting the DateInterval object from the diff() method, make sure that the $startDate variable is in the past by checking the invert property. Then check the y, m, and d properties.

The full list of DateInterval object properties can be found here in the PHP docs.

Where do you go from here?

Now you have a little cheatsheet of common PHP recipes for when you find yourself working with dates and times. Need to get the current date and time? Maybe you need to format a date a certain way, or include the local timezone, or compare dates. All of that is right here!

There are still more methods and functions about date and time that we haven’t discussed, of course — things like calendar-related functions and whatnot. Be sure to keep the PHP Manual’s Date and Time section close by for even more use cases and examples.


PHP Date and Time Recipes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/php-date-and-time-recipes/feed/ 6 356570
How to Convert a Date String into a Human-Readable Format https://css-tricks.com/how-to-convert-a-date-string-into-a-human-readable-format/ https://css-tricks.com/how-to-convert-a-date-string-into-a-human-readable-format/#comments Mon, 25 May 2020 14:13:20 +0000 https://css-tricks.com/?p=311225 I’ll be the first to admit that I’m writing this article, in part, because it’s something I look up often and want to be able to find it next time. Formatting a date string that you get from an API …


How to Convert a Date String into a Human-Readable Format originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I’ll be the first to admit that I’m writing this article, in part, because it’s something I look up often and want to be able to find it next time. Formatting a date string that you get from an API in JavaScript can take many shapes — anything from loading all of Moment.js to have very finite control, or just using a couple of lines to update it. This article is not meant to be comprehensive, but aims to show the most common path of human legibility.

ISO 8601 is an extremely common date format. The “Z” at the end means the time in ISO 8601 format is using the UTC standard, i.e. no timezone. Here’s an example: 2020-05-25T04:00:00Z. When I bring data in from an API, it’s typically in ISO 8601 format.

If I wanted to format the above string in a more readable format, say May 25, 2020, I would do this:

const dateString = '2020-05-14T04:00:00Z'

const formatDate = (dateString) => {
  const options = { year: "numeric", month: "long", day: "numeric" }
  return new Date(dateString).toLocaleDateString(undefined, options)
}

Here’s what I’m doing…

First, I’m passing in options for how I want the output to be formatted. There are many, many other options we could pass in there to format the date in different ways. I’m just showing a fairly common example.

const options = { year: "numeric", month: "long", day: "numeric" }

Next, I’m creating a new Date instance that represents a single moment in time in a platform-independent format.

return new Date(dateString)

Finally, I’m using the .toLocaleDateString() method to apply the formatting options.

return new Date(dateString).toLocaleDateString(undefined, options)

Note that I passed in undefined. Not defining the value in this case means the time will be represented by whatever the default locale is. You can also set it to be a certain area/language. Or, for apps and sites that are internationalized, you can pass in what the user has selected (e.g. 'en-US' for the United States, 'de-DE' for Germany, and so forth). There’s a nice npm package that includes list of more locales and their codes.

Hope that helps you get started! And high fives to future Sarah for not having to look this up again in multiple places. 🤚


How to Convert a Date String into a Human-Readable Format originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/how-to-convert-a-date-string-into-a-human-readable-format/feed/ 12 311225