简体   繁体   中英

How can I store Hour and Minute in a universal time format?

I'm working on a website that teachers are entering their availability based on their local time zone David from California is available Monday and Tuesday at 4 PM PST for example. I want to show that availability to everyone else internationally in their local format John from New York can see David is available at 7 PM EST Is there standard way of doing this without storing local time zone in the db? I was thinking just pick a random date (or even now) and stick the hour/minute to it, save it in UTC and to display it just ignore the date part. does that sound reasonable or there is a better way?

When storing local times, the related timezone data should be stored as well. The most portable identifiers at the moment are IANA representative locations like 'America/New_York'. That way changes to standard and daylight saving offsets are accommodated so that given a particular date, you can get details for one person's time and show it as a date and time for another person's location, adjusting for their offset on that date.

The following shows an algorithm, it uses a rough function from here , but I would strongly suggest using a library like Luxon instead, I just wanted to keep this plain JS.

The following gets a time and location for one user, then displays it as an equivalent time in the location of another user. Hopefully it's something along the lines of what you want to do.

 // Function from https://stackoverflow.com/a/61364310/257182 /* @param {string} isoString - ISO 8601 timestamp without timezone ** eg YYYY-MM-DDTHH:mm:ss or YYYY-MM-DD HH:mm:ss ** @param {string} loc - IANA representateive location ** eg Europe/Berlin ** @param {boolean} returnOffset - if true, return the offset instead of timestamp ** @returns {string} if returnOffset is true, offset is ±HH:mm[:ss] (seconds only if not zero) ** if returnOffset is false, equivalent ISO 8601 UTC timestamp */ let getUTCTime = (function() { let n = 'numeric'; let formatterOpts = {year:n, month:n, day:n, hour:n, minute:n, second:n, hour12: false}; function parse (isoString) { let [Y,M,D,H,m,s] = isoString.split(/[\DT\s]/); return new Date(Date.UTC(Y,M-1,D,H,m,s)); } function toParts(date, formatter) { return formatter.formatToParts(date).reduce((acc, part) => { acc[part.type] = part.value; return acc; }, Object.create(null)); } return function (isoString, loc, returnOffset = false) { formatterOpts.timeZone = loc; let formatter = new Intl.DateTimeFormat('en', formatterOpts); let oDate = parse(isoString); let utcDate = new Date(oDate); let maxLoops = 3, p, diff; do { p = toParts(utcDate, formatter); diff = new Date(Date.UTC(p.year, p.month-1, p.day, p.hour, p.minute, p.second)) - oDate; if (diff) { utcDate.setTime(utcDate.getTime() - diff); } } while (diff && maxLoops--) let dDiff = null; if (maxLoops < 0) { p = toParts(utcDate, formatter); dDiff = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second) - utcDate; let msg = isoString + ' does not exist at ' + loc + ' due to ' + 'daylight saving change-over, shifting into DST'; } let oDiff = dDiff || oDate - utcDate; let sign = oDiff > 0? '+': '-'; oDiff = Math.abs(oDiff); let offH = oDiff / 3.6e6 | 0; let offM = (oDiff % 3.6e6) / 6e4 | 0; let offS = (oDiff % 6e4) / 1e3 | 0; let z = n=>(n<10?'0':'')+n; return returnOffset? `${sign}${z(offH)}:${z(offM)}${offS? ':' + z(offS): ''}`: utcDate.toISOString(); } })(); // Given a local timestmap in format YYYY-MM-DDTHH:mm:ss and // loc as IANA representative location // Return equivalent ISO 8061 UTC timestmap function getUTCString(timestamp, loc) { return getUTCTime(timestamp, loc); } // Given a local timestmap in format YYYY-MM-DDTHH:mm:ss and // loc as IANA representative location // Return offset at loc as ±HH:mm[:ss] // - seconds only included if not zero (typically pre-1900) function getUTCOffset(timestamp, loc) { return getUTCTime(timestamp, loc, true); } /* @param {string} person - name of person ** @param {string} date - date to get times in YYYY-MM-DD format ** @param {string} loc - IANA rep. loc. eg America/New_York ** @returns {string} timestamp for loc */ function showTimes(person, date, loc) { // Get loc and time for person let sourceLoc = data[person].loc; let sourceTime = data[person].time; // Get UTC date for time let sourceDate = date + 'T' + sourceTime + ':00'; let sourceOffset = getUTCOffset(sourceDate, sourceLoc); let utcDate = new Date(sourceDate + sourceOffset); // Return local date for loc return utcDate.toLocaleString('en-CA',{timeZone: loc, timeZoneName:'long', hour12: false}); } let data = { john: { loc: 'America/Los_Angeles', // IANA representative location time: '16:15' // Must be in HH:mm format }, sally: { loc: 'America/New_York', time: '08:30' } } let date = '2020-02-03'; let user1 = 'john'; let user2 = 'sally'; // Standard time // Show John's time in Sally's location on Monday, 3 February 2020 console.log( `${date} ${data[user1].time} for ${user1} in ${data[user1].loc } is\n\ ${showTimes(user1,date, data[user2].loc)} for ${user2}` ); // Daylight saving time // Show Sally's time in John's location on Friday, 26 June 2020 date = '2020-06-26'; console.log( `${date} ${data[user2].time} for ${user2} in ${data[user2].loc } is\n\ ${showTimes(user2,date, data[user1].loc)} for ${user1}` );

Here's an example similar to the above using Luxon:

 let DateTime = luxon.DateTime; let data = { john: { loc: 'America/Los_Angeles', // IANA representative location startTime: '16:15' // Must be in HH:mm format }, sally: { loc: 'America/New_York', startTime: '08:30' } } console.log('----- Standard time -----'); // What is the date and time at Sally's location when John starts on // on Monday, 3 February 2020? let targetDate = '2020-02-03'; let johnStartString = targetDate + 'T' + data.john.startTime; let johnStartDate = DateTime.fromISO(johnStartString, {zone: data.john.loc}); // ISO string for John's startTime console.log('When John starts at: ' + johnStartDate.toISO()); // Create a date for Sally's loc based on John's let sallyDate = johnStartDate.setZone(data.sally.loc); console.log('For Sally it\'s: ' + sallyDate.toISO()); console.log('----- Daylight Saving time -----'); // What is the date and time at John's location when Sally starts on // on Monday, 1 June 2020? targetDate = '2020-06-01'; let sallyStartString = targetDate + 'T' + data.sally.startTime; sallyStartDate = DateTime.fromISO(sallyStartString, {zone: data.sally.loc}); // ISO string for Sally's startTime console.log('When Sally starts at: ' + sallyStartDate.toISO()); // Create a date for John's loc based on Sally's let johnDate = sallyStartDate.setZone(data.john.loc); console.log('For John it\'s: ' + johnDate.toISO());
 <script src="https://cdn.jsdelivr.net/npm/luxon@1.23.0/build/global/luxon.min.js"></script>

I would store:

  • initial time of the day: int
  • end time of the day: int
  • original timezone: string

then, showing that to users is a UI problem. you could calculate dynamically two dates (based on the stored times) in the original timezone and convert it to any target timezone on the fly.

an alternative is checking the time difference between original and target timezones (without calculating any date) and adding it to the initial/end times.. but I guess it's easier to go for the first option as the date classes have that kind of utils .

Keeping track of start and end hours can result in weird timezone errors.

For example, if someone selects Monday 6pm-9pm in EST, that's actually Monday 11pm - Tuesday 2am in UTC. That means the time range stored in UTC is Start: 11pm and End: 2am , which requires lots of code to work around these different scenarios.

A better idea may be to keep track of the starting hour and the number of hours until the ending time (elapsed time).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM