Dates, Times, Zones

When are we!?

Disclaimer #1

This presentation bases on moment and moment-timezone packages.

 

Both are monolithic, a little bloated, and definitely not very performant, but have the best timezone handling.

Disclaimer #2

xkcd

What is...

  • ... date?

    • 2019-10-11
  • ... time?

    • 10:14
  • ... date-time?

    • 2019-10-11 10:14
  • ... timezone?

    • Europe/Warsaw
  • ... timestamp?

    • ​1570781640000

Types of timezones

  • Region-related

    • ​Central European Summer Time
  • City-related

    • Europe/Warsaw
  • Offset-based

    • GMT+2
  • Abbreviated

    • CEST

Question #1

1 day = 24 hours?

No!

Daylight Saving Time

Practice of advancing clocks during warmer months so that darkness falls later each day according to the clock. [...] In other words, there is one 23-hour day in late winter or early spring and one 25-hour day in the autumn.

Wikipedia

dateA = moment.tz('2020-03-29', 'Europe/Warsaw')
dateB = dateA.clone().add(1, 'day')
dateB.diff(dateA, 'hours')
// 23

dateA = moment.tz('2020-10-25', 'Europe/Warsaw')
dateB = dateA.clone().add(1, 'day')
dateB.diff(dateA, 'hours')
// 25

Question #2

Can you skip a day?

Yes!

(American) Samoa

[...] At the end of Thursday, 29 December 2011, Samoa continued directly to Saturday, 31 December 2011, skipping the entire calendar day of Friday, 30 December 2011 and effectively re-drawing the International Date Line.

Wikipedia

How to deal with it?

UTC

Coordinated Universal Time

(yes, this acronym doesn't make any sense)

dateA = moment.utc('2020-03-29')
dateB = dateA.clone().add(1, 'day')
dateB.diff(dateA, 'hours')
// 24

dateA = moment.utc('2020-10-25')
dateB = dateA.clone().add(1, 'day')
dateB.diff(dateA, 'hours')
// 24

But I want my local time!

(a.k.a. how to store and send dates)

  1. Local (e.g. server or client time)
    • Won't work if sides are in different timezones.
    • May break during the year (DST).
  2. UTC + formatting
    • Won't work if sides are in different timezones.
    • May break during the year (DST).
  3. UTC + timezone offset
    • May break during the year (DST).
    • May break with time (Samoa).
  4. UTC + timezone name

 

Question #3

Lib X uses Date objects. How to use it without breaking timezones?

Eh...

Restaurant time

Current local restaurant time

It is possible to "look" into another timezone

Mind the gap DST!
There's no 02:00!

23 hours!

// Calculate non-local date timezone offset.
function getTotalDateOffset(date: Moment) {
  const afterLeapOffset = date.clone().add(+3601, 'second').utcOffset();
  const afterLeapPointOffset = date.clone().endOf('day').utcOffset();
  const afterLeapDiff = afterLeapOffset - afterLeapPointOffset;

  const aheadLeapOffset = date.clone().add(-3601, 'second').utcOffset();
  const aheadLeapPointOffset = date.clone().startOf('day').utcOffset();
  const aheadLeapDiff = aheadLeapOffset - aheadLeapPointOffset;

  const dateOffset = date.clone().utcOffset();
  const weekOffset = date.clone().startOf('week').utcOffset();
  const dateWeekDiff = dateOffset - weekOffset;

  return afterLeapDiff + aheadLeapDiff + dateWeekDiff;
}

// Translate local timestamp to local timezoned date.
function toLocalDate(date: number, timezone: string) {
  const datePrev = moment.tz(date, timezone);
  const dateNext = moment(datePrev.toObject());

  return dateNext.add(getTotalDateOffset(dateNext), 'minute').toDate();
}
moment(+new Date('2019-10-11T10:14')).tz('Europe/Warsaw').toDate()
moment.tz(+new Date('2019-10-11T10:14'), 'Europe/Warsaw').toDate()
moment.utc(+new Date('2019-10-11T10:14')).tz('Europe/Warsaw').toDate()
// Fri Oct 11 2019 10:14:00 GMT+0200 (Central European Summer Time)

moment(+new Date('2019-10-11T10:14')).tz('Pacific/Samoa').toDate()
moment.tz(+new Date('2019-10-11T10:14'), 'Pacific/Samoa').toDate()
moment.utc(+new Date('2019-10-11T10:14')).tz('Pacific/Samoa').toDate()
// Fri Oct 11 2019 10:14:00 GMT+0200 (Central European Summer Time)

toLocalDate(+new Date('2019-10-11T10:14'), 'Europe/Warsaw')
// Fri Oct 11 2019 10:14:00 GMT+0200 (Central European Summer Time)

toLocalDate(+new Date('2019-10-11T10:14'), 'Pacific/Samoa')
// Thu Oct 10 2019 21:14:00 GMT+0200 (Central European Summer Time)

Question #4

My clients are in different timezones. What now!?

Eh...

Restaurant time

// Client
api.getAvailableBookings({ datetime: moment.utc().format() });

// Server
function getAvailableBookings({ datetime }: { datetime: string }) {
  const tz = ...;
  const datetimeLocal = moment(datetime, 'YYYY-MM-DDTHH:mm:ssZ').toDate();
  const datetimeTz = moment(datetimeLocal).tz(tz);

  // Timezoned start of the day: 2020-04-30T14:12 -> 2020-04-30T00:00.
  const dateTz = datetimeTz.clone().startOf('day');
  const offset = (dateTz.utcOffset() - datetimeTz.utcOffset()) * 1000;

  // Timezoned time: 2020-04-30T14:12 -> 14:12 (in milliseconds).
  const timeTz = datetimeTz - dateTz - offset;

  // Remember to return UTC dates, not local nor timezoned ones!
}

// Client
function formatTime(datetime: string) {
  return moment
    .utc(datetime, 'YYYY-MM-DDTHH:mm:ssZ')
    .tz(timezone)
    .format('HH:mm');
}

Question #5

How about repeating events and timezones?

EH!

Seriously, don't.

Don't do it.

If you have to, just copy it as many times as needed.

But those can occur indefinitely!

Just make more.

And only when needed.

But I've found RRULE!

Just don't, OK?

A New Hope

On 8 February 2018, the European Parliament voted to ask the European Commission to re-evaluate DST in Europe. After a web survey, in which 4.6 million European citizens participated, showed high support for not switching clocks twice annually [...] On 4 March 2019, the European Parliament Transport and Tourism Committee approved the Commission's proposal by 23 votes to 11. The start date will however be postponed until 2021 at the earliest, to ensure a smooth transition [...] Under the draft directive, member states would be able to choose whether to remain on their current summer time [...] or their current winter time [...].

Wikipedia

TL;DR;

  • Use ISO 8601 formats.
  • Prefer dates over timestamps.
  • Store UTC dates and their timezones.
  • Mind the gap while operating on dates.

Questions?