Skip to content

Instantly share code, notes, and snippets.

@jeremyevans6
Created September 24, 2020 03:13
Show Gist options
  • Save jeremyevans6/548ee8a78b60666ee26476bbe0e442f1 to your computer and use it in GitHub Desktop.
Save jeremyevans6/548ee8a78b60666ee26476bbe0e442f1 to your computer and use it in GitHub Desktop.
Time Zones for ShareTribe v10
//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)
}
# == 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