Created
September 24, 2020 03:13
-
-
Save jeremyevans6/548ee8a78b60666ee26476bbe0e442f1 to your computer and use it in GitHub Desktop.
Time Zones for ShareTribe v10
This file contains hidden or 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
//This file lives in app/assets/javascripts/ | |
//It has to be required in app/assets/javascripts/application.js, not shown here | |
// | |
//It uses browser timezones to correct the client's display of dates and times in three areas of a Sharetribe site. | |
//By not changing the database, these times will always be UTC in the backend. Indeed, their values remain UTC for the client, | |
//though the display of the times in the DOM is modified. | |
const timeRegex = /((1[0-2]|0?[1-9]):([0-5][0-9]) ?([AaPp][Mm]))/g; | |
const daysOfWeekRegex = /(Sun|Mon|Tue|Wed|Thu|Fri|Sat)/g; | |
const monthsRegex = /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/g; | |
const dateRegex = /[1-3][0-9]/u; | |
const yearRegex = /[0-9]{4}/g; | |
const fullDateRegex = /(Sun|Mon|Tue|Wed|Thu|Fri|Sat]), (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [1-3][0-9], [0-9]{4}/; | |
const monthsMap = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; | |
const order = ['1:00 am', '2:00 am', '3:00 am', '4:00 am', '5:00 am', '6:00 am', '7:00 am', '8:00 am', '9:00 am', '10:00 am', '11:00 am', '12:00 pm', '1:00 pm', '2:00 pm', '3:00 pm', '4:00 pm', '5:00 pm', '6:00 pm', '7:00 pm', '8:00 pm', '9:00 pm', '10:00 pm', '11:00 pm','12:00 am'] | |
const firstSpaceRegex = /^\s/ | |
const valueRegex = /value=\"(.*?)\"/ | |
var offset = new Date().getTimezoneOffset(); | |
var newHtml; | |
//offset is in minutes from UTC | |
function timeConvert(n) { | |
var num = n; | |
var hours = (num / 60); | |
var rhours = Math.floor(hours); | |
var minutes = (hours - rhours) * 60; | |
var rminutes = Math.round(minutes); | |
if (rminutes < 10) { | |
var rminutesFormat = "0" + rminutes | |
} else { | |
var rminutesFormat = rminutes | |
} | |
return rhours + ":" + rminutesFormat; | |
} | |
//check for Inbox, Availability, and Booking | |
if ($('.initiate-transaction-booking-value').length) { | |
//Inbox | |
var html = $('.initiate-transaction-booking-value').html() | |
var rawHtmlTimes = html.match(timeRegex) | |
var updateDate = false | |
var dayBefore = false | |
var dayAfter = false | |
$(rawHtmlTimes).each(function() { | |
var htmlSplit = this.split(' ') | |
var htmlTime = htmlSplit[0] | |
var htmlAmPm = htmlSplit[1] | |
var hour = htmlTime.replace(':00', '') | |
if (htmlAmPm == 'pm') { | |
var minutes = parseInt(hour) * 60 + 12 * 60 | |
} else { | |
var minutes = parseInt(hour) * 60 | |
} | |
var minutesOffset = minutes - offset | |
var hourOffset = minutesOffset / 60 | |
//correct date, if needed | |
if (hourOffset < 1 || hourOffset > 24) { | |
updateDate = true | |
if (hourOffset < 1) { | |
hourOffset = 24 - hourOffset | |
dayBefore = true | |
} | |
if (hourOffset > 24) { | |
hourOffset = hourOffset - 24 | |
dayAfter = true | |
} | |
} | |
if (hourOffset > 12) { | |
var newHour = hourOffset - 12 | |
var newHourHtml = newHour + ':00 pm' | |
} else { | |
var newHour = hourOffset | |
var newHourHtml = newHour + ':00 am' | |
} | |
html = html.replace(firstSpaceRegex, '').replace(this, newHourHtml) | |
newHtml = html | |
}) //end times loop | |
if (updateDate) { | |
var nextDate | |
var rawHtmlDayOfWeek = html.match(daysOfWeekRegex) | |
var rawHtmlDate = html.match(dateRegex) | |
var rawHtmlMonth = html.match(monthsRegex) | |
var rawHtmlYear = html.match(yearRegex) | |
var dateOnly = parseInt(rawHtmlDate) | |
var monthOnly = parseInt(monthsMap.indexOf(rawHtmlMonth[0])) + 1 | |
var yearOnly = parseInt(rawHtmlYear) | |
var date = monthOnly + "/" + dateOnly + "/" + yearOnly | |
var dateObj = new Date(date) | |
if (dayBefore) { | |
nextDate = dateObj.setTime(dateObj.getTime() - 1 * 86400000) | |
} | |
if (dayAfter) { | |
nextDate = dateObj.setTime(dateObj.getTime() + 1 * 86400000) | |
} | |
var newDate = dateObj.toLocaleString() | |
var newDateYearSplit = newDate.split(',') | |
var newDateSplit = newDateYearSplit[0].split('/') | |
var dateVal = newDateSplit[0] + '/' + newDateSplit[1] + '/' + newDateSplit[2] | |
newHtml = newHtml.replace(fullDateRegex, dateVal) | |
} | |
//time to change the DOM | |
$('.initiate-transaction-booking-value').html(newHtml) | |
} | |
//Availability | |
$('.listing-view-admin-links').ready(function() { | |
if ($('.listing-view-admin-links').length || window.location.hash == '#manage-working-hours') { | |
function availabilityClear($changed) { | |
var $endTimeOptions = $changed.find('.option') | |
$endTimeOptions.each(function() { | |
$(this).prop('disabled', false) | |
}) | |
} | |
function availabilityChange($changed, inner) { | |
//var val = parseInt($changed.val()) | |
var $endTimeOptions = $changed.find('option') | |
var starTimeSelected = $changed.parents('.timeSlot').find('.starTime select option:selected', this).html() | |
//debugger | |
$endTimeOptions.each(function() { | |
$(this).prop('disabled', false) | |
}) | |
var startTimeReached = false | |
var beforeStartTime = [] | |
for (i = 0; i < order.length; i++) { | |
if (inner !== order[i]) { | |
beforeStartTime.push(order[i]) | |
} else if (inner == order[i]) { | |
beforeStartTime.push(order[i]) | |
break; | |
} | |
} | |
$endTimeOptions.each(function() { | |
if (beforeStartTime.indexOf($(this).html()) !== -1) { | |
$(this).prop('disabled', true) | |
} else { | |
$(this).prop('disabled', false) | |
} | |
}) | |
} //end availabilityChange function | |
function availability(e) { | |
$('.timeSlot select').each(function() { | |
var $select = $(this) | |
var currOptionsArr = [] | |
var unsortedOptionsArr = [] | |
/* | |
var originalVal = parseInt($select.val()) | |
var valOffset = Math.floor(offset/60) | |
var newVal = originalVal+valOffset | |
console.log("originalVal "+originalVal) | |
console.log("valOffset "+valOffset) | |
console.log("newVal "+newVal) | |
*/ | |
$select.find('option').each(function() { | |
currOptionsArr.push(this.outerHTML) | |
}) | |
$select.find('option:not(.timezoned)').each(function() { | |
var html = $(this).html() | |
var val = $(this).val() | |
var rawHtmlTimes = html.match(timeRegex) | |
if (html !== undefined && html !== ' ' && parseInt(val) !== 0 && parseInt(val) !== 25) { | |
$(rawHtmlTimes).each(function() { | |
var htmlSplit = rawHtmlTimes[0].split(' ') | |
var htmlTime = htmlSplit[0] | |
var htmlAmPm = htmlSplit[1] | |
var hour = htmlTime.replace(':00', '') | |
if (htmlAmPm == 'pm') { | |
var minutes = parseInt(hour) * 60 + 12 * 60 | |
} else { | |
var minutes = parseInt(hour) * 60 | |
} | |
var minutesOffset = minutes - offset | |
var hoursOffset = minutesOffset / 60 | |
var convertedOffset = timeConvert(minutesOffset) | |
if (hoursOffset == 0) { | |
var adjMinutes = minutesOffset | |
var newHourHtml = timeConvert(adjMinutes) + ' am' | |
} else if (hoursOffset < 0) { | |
var adjMinutes = minutesOffset + 12 * 60 | |
var newHourHtml = timeConvert(adjMinutes) + ' pm' | |
} else if (hoursOffset < 12) { | |
var adjMinutes = minutesOffset | |
var newHourHtml = timeConvert(adjMinutes) + ' am' | |
} else if (hoursOffset == 12) { | |
var adjMinutes = minutesOffset | |
var newHourHtml = timeConvert(adjMinutes) + ' pm' | |
} else if (hoursOffset < 24) { | |
var adjMinutes = minutesOffset - 12 * 60 | |
var newHourHtml = timeConvert(adjMinutes) + ' pm' | |
} else if (hoursOffset == 24) { | |
var adjMinutes = minutesOffset - 12 * 60 | |
var newHourHtml = timeConvert(adjMinutes) + ' am' | |
} else if (hoursOffset > 24) { | |
var adjMinutes = minutesOffset - 24 * 60 | |
var newHourHtml = timeConvert(adjMinutes) + ' am' | |
} | |
if (hoursOffset == 6) { | |
var newHourHtml = timeConvert(adjMinutes) + ' pm' | |
} | |
if (hoursOffset == 18) { | |
var newHourHtml = timeConvert(adjMinutes) + ' am' | |
} | |
if (newHourHtml == "0:00 am") { | |
newHourHtml = '12:00 am' | |
} | |
html = newHourHtml | |
}) //end times loop | |
//time to change the DOM | |
html = html.replace(firstSpaceRegex, '') | |
} | |
if (parseInt(this.value) == 0 || parseInt(this.value) == 25) { | |
this.remove() | |
} else { | |
$(this).html(html) | |
} | |
}) //end option loop | |
//time to sort | |
$select.find('option:not(.timezoned)').each(function() { | |
unsortedOptionsArr.push(this.outerHTML) | |
}) | |
var sortedHtml = [] | |
var sortedVal = [] | |
$select.find('option:not(.timezoned)').each(function(index) { | |
var thisOption = this | |
unsortedOptionsArr.forEach(function(a, b, c) { | |
var thisArrOption = a | |
if (thisOption.outerHTML == thisArrOption) { | |
for (i = 0; i < order.length; i++) { | |
if (thisArrOption.match(timeRegex) == order[i]) { | |
sortedHtml.splice(i, 0, thisOption.outerHTML.match(timeRegex)) | |
sortedVal.splice(i, 0, thisOption.outerHTML.match(valueRegex)[1]) | |
//return false; | |
//debugger | |
} | |
} | |
} | |
}) //unsortedOptionsArr end | |
}) //end options loop | |
$select.find('option:not(.timezoned)').each(function(index) { | |
if (sortedHtml !== undefined) { | |
this.innerHTML = sortedHtml[index][0] | |
this.value = sortedVal[index] | |
$(this).addClass('timezoned') | |
} | |
}) | |
}) //end select loop | |
console.log('timezoned') | |
$('.timeSlot .endTime select:not(.mutable)').each(function() { | |
$(this).on('click touchstart', function() { | |
console.log($("option:selected", this).html()) | |
setTimeout(availabilityClear($(this).parents('.timeSlot').find(".starTime select"), 777)) | |
setTimeout(availabilityChange($(this), $(this).parents('.timeSlot').find(".starTime select option:selected", this).html()), 888) | |
}) | |
$(this).addClass('mutable') | |
}) | |
} | |
$('.icon-with-text-container:not(.timezoned),.addMore:not(.timezoned)').on('touchstart click', function(e) { | |
$('.react-form-select').ready(function() { | |
setTimeout(availability, 555) | |
setTimeout(function(){ | |
$('#enable-sun').click() | |
setTimeout(function(){ | |
$('#enable-sun').click() | |
},222) | |
},999) | |
}) | |
}) | |
$('.react-form-select').ready(function() { | |
setTimeout(availability, 555) | |
setTimeout(function(){ | |
$('#enable-sun').click() | |
setTimeout(function(){ | |
$('#enable-sun').click() | |
},222) | |
},999) | |
}) | |
} | |
}) | |
//Booking | |
if ($('.datepicker-per-hour').length) { | |
function bookingTimezone() { | |
$('.datepicker-per-hour select option:not(.timezoned):not([disabled])').each(function() { | |
var html = $(this).html() | |
var rawHtmlTimes = html.match(timeRegex) | |
$(rawHtmlTimes).each(function() { | |
var htmlSplit = this.split(' ') | |
var htmlTime = htmlSplit[0] | |
var htmlAmPm = htmlSplit[1] | |
var hour = htmlTime.replace(':00', '') | |
if (htmlAmPm == 'pm') { | |
var minutes = parseInt(hour) * 60 + 12 * 60 | |
} else { | |
var minutes = parseInt(hour) * 60 | |
} | |
var minutesOffset = minutes - offset | |
var hoursOffset = minutesOffset / 60 | |
var convertedOffset = timeConvert(minutesOffset) | |
if (hoursOffset < 1) { | |
var adjMinutes = minutesOffset + 12 * 60 | |
var newHourHtml = timeConvert(adjMinutes) + ' pm' | |
} else if (hoursOffset < 13) { | |
var adjMinutes = minutesOffset | |
var newHourHtml = timeConvert(adjMinutes) + ' am' | |
} else if (hoursOffset < 25) { | |
var adjMinutes = minutesOffset - 12 * 60 | |
var newHourHtml = timeConvert(adjMinutes) + ' pm' | |
} else if (hoursOffset > 24) { | |
var adjMinutes = minutesOffset - 24 * 60 | |
var newHourHtml = timeConvert(adjMinutes) + ' am' | |
} | |
if (hoursOffset == 6) { | |
var newHourHtml = timeConvert(adjMinutes) + ' pm' | |
} | |
if (hoursOffset == 18) { | |
var newHourHtml = timeConvert(adjMinutes) + ' am' | |
} | |
html = html.replace(this, newHourHtml) | |
newHtml = html | |
}) //end times loop | |
//time to change the DOM | |
$(this).html(newHtml) | |
$(this).addClass('timezoned') | |
}) //end options loop | |
$('.datepicker-per-hour select option[disabled]').each(function() { | |
if ($(this).html() !== 'Select one') { | |
$(this).html('') | |
} | |
}) | |
} | |
$('.datepicker-per-hour select,input').on('touchstart mousedown', bookingTimezone) | |
} |
This file contains hidden or 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
# == Schema Information | |
# | |
# Table name: listing_working_time_slots | |
# | |
# id :bigint not null, primary key | |
# listing_id :integer | |
# week_day :integer | |
# from :string(255) | |
# till :string(255) | |
# created_at :datetime not null | |
# updated_at :datetime not null | |
# | |
# Indexes | |
# | |
# index_listing_working_time_slots_on_listing_id (listing_id) | |
# | |
class Listing::WorkingTimeSlot < ApplicationRecord | |
belongs_to :listing | |
validate :from_is_less_than_till | |
enum week_day: {sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6} | |
scope :by_week_day, ->(day) { where(week_day: day) } | |
scope :ordered, -> { order('listing_working_time_slots.week_day ASC, listing_working_time_slots.from ASC') } | |
def covers_booking?(booking) | |
start_time = booking.start_time | |
year = start_time.year | |
month = start_time.month | |
day = start_time.day | |
from_time = Time.zone.parse("#{year}/#{month}/#{day} #{from}") | |
till_time = Time.zone.parse("#{year}/#{month}/#{day} #{till}") | |
from_time <= booking.start_time && till_time >= booking.end_time | |
end | |
private | |
def from_is_less_than_till | |
now = Time.zone.now | |
from_time = Time.zone.parse("#{now.year}/#{now.month}/#{now.day} #{from}") | |
till_time = Time.zone.parse("#{now.year}/#{now.month}/#{now.day} #{till}") | |
if from_time == till_time | |
errors.add :from, :from_must_be_less_than_till | |
errors.add :till, :from_must_be_less_than_till | |
elsif from_time > till_time | |
if from[0].to_i == till[0].to_i+1 | |
from_time = Time.zone.parse("#{now.year}/#{now.month}/#{now.day} 00:00") | |
till_time = Time.zone.parse("#{now.year}/#{now.month}/#{now.day} 24:00") | |
else | |
from_time = Time.zone.parse("#{now.year}/#{now.month}/#{now.day} #{till}") | |
till_time = Time.zone.parse("#{now.year}/#{now.month}/#{now.day} #{from}") | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment