Skip to content

Instantly share code, notes, and snippets.

@pixelbrackets
Created February 25, 2021 11:06
Show Gist options
  • Save pixelbrackets/450c76b43e42ddadf7b5b0282ddaff2c to your computer and use it in GitHub Desktop.
Save pixelbrackets/450c76b43e42ddadf7b5b0282ddaff2c to your computer and use it in GitHub Desktop.
Date Formats (2017-02-21)
Valid till: <span class="datetime" data-datetime="2017-02-21 17:00">2017-02-21 17:00 (GMT)</span>
<script>
// Given: Date and time in UTC timezone and international format
// Returned: Date and time in local timezone and international format
// Note: Returning localised date formats is not possible in JavaScript
// without the help of a library like moment.js!
// Extend date object with format method
Date.prototype.format = function(format) {
format = format || 'YYYY-MM-DD hh:mm';
var zeropad = function(number, length) {
number = number.toString();
length = length || 2;
while(number.length < length)
number = '0' + number;
return number;
},
formats = {
YYYY: this.getFullYear(),
MM: zeropad(this.getMonth() + 1),
DD: zeropad(this.getDate()),
hh: zeropad(this.getHours()),
mm: zeropad(this.getMinutes()),
O: (function() {
localDate = new Date;
sign = (localDate.getTimezoneOffset() > 0) ? '-' : '+';
offset = Math.abs(localDate.getTimezoneOffset());
hours = zeropad(Math.floor(offset / 60));
minutes = zeropad(offset % 60);
return sign + hours + ":" + minutes;
})()
},
pattern = '(' + Object.keys(formats).join(')|(') + ')';
return format.replace(new RegExp(pattern, 'g'), function(match) {
return formats[match];
});
};
function dateFromUtcString(datestring) {
// matches »YYYY-MM-DD hh:mm«
var m = datestring.match(/(\d+)-(\d+)-(\d+)\s+(\d+):(\d+)/);
return new Date(Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], 0));
}
function dateFromUtcTimestamp(datetimestamp) {
return new Date(parseInt(datetimestamp) * 1000)
}
function dateToUtcString(datelocal) {
return new Date(
datelocal.getUTCFullYear(),
datelocal.getUTCMonth(),
datelocal.getUTCDate(),
datelocal.getUTCHours(),
datelocal.getUTCMinutes(),
datelocal.getUTCSeconds()
).format();
}
function dateToUtcTimestamp(datelocal) {
return (Date.UTC(
datelocal.getUTCFullYear(),
datelocal.getUTCMonth(),
datelocal.getUTCDate(),
datelocal.getUTCHours(),
datelocal.getUTCMinutes(),
datelocal.getUTCSeconds()
) / 1000);
}
function convertAllDatetimeFields() {
datefields = document.getElementsByClassName('datetime')
for(var i=0; i<datefields.length; i++) {
dateUTC = datefields[i].getAttribute('data-datetime');
datefields[i].setAttribute('title', dateUTC + ' (GMT)');
datefields[i].innerHTML = dateFromUtcString(dateUTC).format('YYYY-MM-DD hh:mm (GMT O)');
}
}
// ondocumentload: Search for all datetime fields and convert the time to local timezone
document.addEventListener('DOMContentLoaded', function(event) {
convertAllDatetimeFields();
});
// Tests: Open console to see the test actions
var dateUtcString = '2017-02-21 17:00';
var dateUtcTimestamp = '1487696400';
console.log('UTC date string:');
console.log(dateUtcString); // "2017-02-21 17:00"
console.log('UTC date timestamp:');
console.log(dateUtcTimestamp); // "1487696400"
console.log('UTC date timestamp converted:');
console.log(dateFromUtcTimestamp(dateUtcTimestamp).format()); // "2017-02-21 18:00" (using Berlin)
console.log('UTC date string converted:');
console.log(dateFromUtcString(dateUtcString).format()); // "2017-02-21 18:00" (using Berlin)
console.log('UTC date string converted with offset:');
console.log(dateFromUtcString(dateUtcString).format('YYYY-MM-DD hh:mm (GMT O)')); // "2017-02-21 18:00 (GMT +01:00)" (using Berlin)
console.log('UTC date string converted, then back to timestamp:');
console.log(dateToUtcTimestamp(dateFromUtcString(dateUtcString))); // "1487696400" (using Berlin)
</script>
<!--
@pixelbrackets
JSFiddle: <script async src="//jsfiddle.net/pixelbrackets/cfugo633/embed/html,result/"></script>
-->
<!--
tl;dr To render a date and time in a clients timezone,
servers should always render UTC and use JavaScript to convert
from UTC timezone to the local timezone. This however is simply
not possible with the standard Date object in JavaScript.
It is necessary to use custom parsing scripts.
Prelude
-------
Task: Convert datetime strings given by a server
to a localised time and timezone.
For example if the service offers an international page,
and clients should get shown a time based on their location
(eg. “This link is valid till 2017-02-21 17:00”).
Problem: The server cant localise the string without
knowledge of the users timezone and a set of local date formats.
Solution: The server always renders the datetime in UTC timezone,
JavaScript is used to convert this date to the local timezone.
The output format is the somewhat international format
»YYYY-MM-DD hh:mm«, to avoid translations of the weekday (see
explanation below, why this is avoided).
Pitfalls: JavaScript depends on the clients system time,
which may be wrong. Showing UTC dates only would prevent any
wrong or doubtful conversions.
Timezones may change over the years, therefore using the
current timezone offset is no valid solution to refer to
exact moments in history.
Daylight savings time may cause issues with future dates.
PHP
---
To store and use dates and times in PHP it is a best practice
to always use the UTC timezone.
The PHP script should not rely on the servers timezone in any case,
as this timezone may change on cloud servers or is not set at all.
It is recommended to use UTC and convert to a desired timezone instead.
@see https://phpbestpractices.org/#working-with-dates-and-times
When converting to different timezones it is recommend to use UTC
as base value. Then either convert to a desired time zone with PHP,
or use a clientside script to convert to the timezone of the client.
@see http://stackoverflow.com/a/2532962/3894752
-->
<!--
JavaScript
----------
UTC is the base time zone to convert datetime strings to different
time zones.
Base: 2017-02-21 17:00 UTC
Same as London GMT: 2017-02-21 17:00 UTC
Same as New York EST: 2017-02-21 12:00 UTC -5
Same as San Francisco PST: 2017-02-21 09:00 UTC -8
Same as Sidney AEDT: 2017-02-22 04:00 UTC +11
Same as Afghanistan AFT: 2017-02-21 21:30 UTC +4:30
Same as Berlin CET: 2017-02-21 18:00 UTC +1
@see https://www.timeanddate.com/time/zones/
Altough UTC and GMT have different physical meanings,
they may be considered equivalent in regard to most date functions.
UTC is the preferred term to use in technical documents,
GMT is more known by users. Thats why a lot of scripts refer to
the term »UTC« in their methods but output strings with »GMT«.
Notice that UTC is also known as »Zulu« or »Z« time. Four different
terms, same meaning.
@see http://www.differencebetween.com/difference-between-gmt-and-utc/
The Date object expects the given string to be UTC if it has the
the format »2017-02-21«. If the string is given in ISO-8601 format
however (»2017-02-21T17:00:00«), then it should be treated as local time.
The string may have several other formats as well, and all of these
may be parsed differently.
@see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse
Even more confusing than the variable input parameter of the Date object
is the output. If the Date object is parsed to a string, each browser
will return a different value. Some output the long ISO format like
»2017-02-21T17:00:00.000Z«, some a string with locale support like
»Tue Feb 21 2017 18:00:00 GMT+0100 (CET)«.
The format »YYYY-MM-DD hh:mm« is quite international and may be
understood in most places. To output a date in the format of a
specific language it is necessary to have a list of formats and translations.
The date »2017-02-21« is written as »02/21/2017 (Tuesday)« in
English (United States), but »21/02/2017 (Tuesday)« in English (United Kingdom), and
»21.02.2017 (Dienstag)« in German (Germany).
The best method to parse and display dates in different languages
is using a library like moment.js (http://momentjs.com). To detect a
timezone and convert a time to different timezones jsTimezoneDetect
(https://bitbucket.org/pellepim/jstimezonedetect/downloads) may be used
together with moment-timezone.js (http://momentjs.com/timezone/).
The following scripts are short solutions to detect the timezone
offset, convert to a local timezone and output the datetime in
the international format only. Sufficient enough to save some dependencies.
-->
Open the console to see the output!
<iframe width="100%" height="300" src="//jsfiddle.net/L7hq8hx7/4/embedded/js/" allowfullscreen="allowfullscreen" frameborder="0"></iframe>
<script>
// Date and time in UTC timezone and international format
var dateUTC = '2017-02-21 17:00'; // This format only! No “UTC” etc.
console.log('UTC date string:');
console.log(dateUTC); // "2017-02-21 17:00"
// Output Date object in custom format
// @see http://stackoverflow.com/a/28407931/3894752
Date.prototype.format = function(format) {
// set default format if function argument not provided
format = format || 'YYYY-MM-DD hh:mm';
var zeropad = function(number, length) {
number = number.toString();
length = length || 2;
while(number.length < length)
number = '0' + number;
return number;
},
// here you can define your formats
formats = {
YYYY: this.getFullYear(),
MM: zeropad(this.getMonth() + 1),
DD: zeropad(this.getDate()),
hh: zeropad(this.getHours()),
mm: zeropad(this.getMinutes())
},
pattern = '(' + Object.keys(formats).join(')|(') + ')';
return format.replace(new RegExp(pattern, 'g'), function(match) {
return formats[match];
});
};
var dateUTCobject = new Date(dateUTC);
console.log('UTC date object:');
console.log(dateUTCobject); // "Tue Feb 21 2017 17:00:00 GMT+0100 (CET)"
console.log('UTC date with custom format:');
console.log(dateUTCobject.format('hh:mm YYYY/MM/DD')); // "17:00 2017/02/21"
// Convert UTC date to local timezone (UTC +/- timezone offset)
// @see http://stackoverflow.com/a/24027513/3894752
// with bugfix for wrong algebraic sign in offset calculation
function ConvertUTCTimeToLocalTime(UTCDateString) {
var convertdLocalTime = new Date(UTCDateString);
var hourOffset = convertdLocalTime.getTimezoneOffset() / 60;
convertdLocalTime.setHours( convertdLocalTime.getHours() - hourOffset );
return convertdLocalTime;
}
var dateLocal = ConvertUTCTimeToLocalTime(dateUTC);
console.log('UTC to local time:');
console.log(dateLocal); // "Tue Feb 21 2017 18:00:00 GMT+0100 (CET)" (using Berlin)
console.log('UTC to local time formatted:');
console.log(dateLocal.format()); // "2017-02-21 18:00" (using Berlin)
// Calculate timezone offset in human readable form
// @see http://stackoverflow.com/a/13016136/3894752
function pad(value) {
return value < 10 ? '0' + value : value;
}
function createOffset(date) {
var sign = (date.getTimezoneOffset() > 0) ? "-" : "+";
var offset = Math.abs(date.getTimezoneOffset());
var hours = pad(Math.floor(offset / 60));
var minutes = pad(offset % 60);
return sign + hours + ":" + minutes;
}
console.log('Local Timezone offset to UTC:');
console.log(createOffset(new Date)); // "+01:00" (using Berlin)
// 🏁 Final conversion:
console.log('Final - Convert UTC date to local date and show offset:');
console.log(ConvertUTCTimeToLocalTime(dateUTC).format('YYYY-MM-DD hh:mm') + ' (UTC ' + createOffset(new Date) + ')'); // "2017-02-21 18:00 (UTC +01:00)" (using Berlin)
/*
Final? Wrong! Internet Explorer kills the whole magic.
Chrome and Firefox for example support »2017-02-21 17:00« as
UTC date format, because they have an own fallback if the given UTC
date has no valid format. IE doesn't and returns errors instead.
Edge however does support the fallback, only IE <= 11 are affected.
But IE does not support the ISO format »2017-02-21T17:00:00« as well.
All browsers also mix the UTC timezone fallback with local timezones,
so no consistent behavior may be expected.
@see http://codecoding.com/how-different-browsers-implement-date-function/
Because of this annoyance it is reccommended to always parse date strings
manually and dont rely on the browsers and the default Date.parse() method.
> Because of the variances in parsing of date strings, it is
> recommended to always manually parse strings as results are
> inconsistent, especially across different ECMAScript implementations
> where strings like "2015-10-12 12:00:00" may be parsed to as NaN,
> UTC or local timezone.
@see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse#Description
*/
// Parse a datestring with a known format, create a UTC date,
// convert to a unix timestamp and pass it safely to the Date.parse() function,
// which then converts the time to the local timezone automatically.
// This method is safe for most browsers.
function dateFromString(str) {
// matches »YYYY-MM-DD hh:mm«
var m = str.match(/(\d+)-(\d+)-(\d+)\s+(\d+):(\d+)/);
//var conv = new Date(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], 0);
var conv = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], 0));
return conv;
}
console.log('UTC date string parsed manually:');
console.log(dateFromString(dateUTC)); // "Tue Feb 21 2017 18:00:00 GMT+0100 (CET)" (using Berlin)
console.log('UTC date string parsed manually, converted to local timezone and formatted:');
console.log(dateFromString(dateUTC).format()); // "2017-02-21 18:00" (using Berlin)
// Alternative: Parse UTC given as timestamp and pass it to Date.parse() function
function dateFromTimestamp(timestamp) {
return new Date(parseInt(timestamp) * 1000)
}
console.log('Alternative: UTC given as unix timestamp, converted to local timezone and formatted:');
console.log(dateFromTimestamp('1487696400').format()); // "2017-02-21 18:00" (using Berlin)
// Final 2
console.log('Final 2 - Convert UTC date in most browsers to local date and show offset:');
console.log(dateFromString(dateUTC).format() + ' (UTC ' + createOffset(new Date) + ')'); // "2017-02-21 18:00 (UTC +01:00)" (using Berlin)
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment