Created
February 25, 2021 11:06
-
-
Save pixelbrackets/450c76b43e42ddadf7b5b0282ddaff2c to your computer and use it in GitHub Desktop.
Date Formats (2017-02-21)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
--> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- | |
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