walltime-js
Version:
A javascript library for easily translating a UTC time to a "Wall Time" for a particular time zone and back.
254 lines (203 loc) • 9.05 kB
text/coffeescript
# A shim for IE8+9 Array.indexOf
Array::indexOf or= (item) ->
for x, i in this
return i if x is item
return -1
# Some helpers for going between day names and indices.
Days =
DayShortNames: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
DayIndex: (name) ->
@DayShortNames.indexOf name
DayNameFromIndex: (dayIdx) ->
@DayShortNames[dayIdx]
AddToDate: (dt, days) ->
Time.MakeDateFromTimeStamp (dt.getTime() + (days * Milliseconds.inDay))
# Some helpers for going between month names and indices
Months =
MonthsShortNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
CompareRuleMatch: new RegExp "([a-zA-Z]*)([\\<\\>]?=)([0-9]*)"
MonthIndex: (shortName) ->
@MonthsShortNames.indexOf shortName.slice(0, 3)
IsDayOfMonthRule: (str) ->
str.indexOf(">") > -1 or str.indexOf("<") > -1 or str.indexOf("=") > -1
IsLastDayOfMonthRule: (str) ->
str[0..3] == "last"
DayOfMonthByRule: (str, year, month) ->
# Parse our onStr into the components
ruleParse = @CompareRuleMatch.exec str
if !ruleParse
throw new Error "Unable to parse the 'on' rule for #{str}"
# Whoa, destructured assignment? Hell yeah!
[dayName, testPart, dateIndex] = ruleParse[1..3]
dateIndex = parseInt dateIndex, 10
if dateIndex is NaN
throw new Error "Unable to parse the dateIndex of the 'on' rule for #{str}"
dayIndex = helpers.Days.DayIndex dayName
# Set up the compare functions based on the conditional parsed from the onStr
compares =
">=": (a, b) -> a >= b
"<=": (a, b) -> a <= b
">": (a, b) -> a > b
"<": (a, b) -> a < b
"=": (a, b) -> a == b
compareFunc = compares[testPart]
if !compareFunc
throw new Error "Unable to parse the conditional for #{testPart}"
# Begin at the beginning of the month, at worst we are iterating 30 or so extra times.
testDate = helpers.Time.MakeDateFromParts year, month
# Go forward one day at a time until we get a matching day of the week (Sun) and the compare of the date of the month passes (8 >= 8)
while !(dayIndex == testDate.getUTCDay() and compareFunc(testDate.getUTCDate(), dateIndex))
testDate = helpers.Days.AddToDate testDate, 1
testDate.getUTCDate()
LastDayOfMonthRule: (str, year, month) ->
# Get the last day with a matching name
dayName = str[4..]
# TODO: Always has a capital first letter?
dayIndex = helpers.Days.DayIndex dayName
# To get the last day of the month we set the date to the first day of next month, and move back one day.
# Set the date to the first day of the next month
if month < 11
lastDay = helpers.Time.MakeDateFromParts year, (month + 1)
else
# Account for going over to the next year.
lastDay = helpers.Time.MakeDateFromParts year + 1, 0
# Move back one day to the last day of the current month.
lastDay = helpers.Days.AddToDate lastDay, -1
# Iterate backward until our dayIndex matches the last days index
while lastDay.getUTCDay() != dayIndex
lastDay = helpers.Days.AddToDate lastDay, -1
return lastDay.getUTCDate()
Milliseconds =
inDay: 86400000
inHour: 3600000
inMinute: 60000
inSecond: 1000
# Some helpers for dealing with time values
Time =
Add: (dt, hours = 0, mins = 0, secs = 0) ->
newTs = dt.getTime() + (hours * Milliseconds.inHour) + (mins * Milliseconds.inMinute) + (secs * Milliseconds.inSecond)
@MakeDateFromTimeStamp newTs
# Slightly different cases for GMT Strings, so we have them separated out
# *NOTE*: Seconds will be NaN if they are not present
ParseGMTOffset: (str) ->
reg = new RegExp("(-)?([0-9]*):([0-9]*):?([0-9]*)?")
matches = reg.exec str
result = if matches then (parseInt match, 10 for match in matches[2..]) else [0,0,0]
# Inject in whether we are negative
isNeg = matches && matches[1] == "-"
result.splice 0, 0, isNeg
result
# Parse a time string, usually in the form "2:00". A 24-hour time string.
ParseTime: (str) ->
reg = new RegExp("(\\d*)\\:(\\d*)([wsugz]?)")
matches = reg.exec str
return [0, 0, ''] unless matches
timeParts = (parseInt match, 10 for match in matches[1..2])
# Append on the qualifier at the end
qual = if matches[3] then matches[3] else ''
timeParts.push qual
# return the parts
timeParts
# Applies a GMT Style Offset. -5:50:36 would return a new time that is 5 hours, 50 minutes and 36 seconds ahead of the passed in one
# This is used to reconcile a standard time to UTC, however, if reverse is passed we can convert from UTC to Standard
ApplyOffset: (dt, offset, reverse) ->
offset_ms = (Milliseconds.inHour * offset.hours) + (Milliseconds.inMinute * offset.mins) + (Milliseconds.inSecond * offset.secs)
offset_ms = offset_ms * -1 if !offset.negative
if reverse
offset_ms = offset_ms * -1
@MakeDateFromTimeStamp(dt.getTime() + offset_ms)
# Applies a SAVE value by adjusting the time forward by the time passed in.
ApplySave: (dt, save, reverse) ->
if reverse != true
reverse = false
@ApplyOffset dt, { negative: true, hours: save.hours, mins: save.mins, secs: 0}, reverse
UTCToWallTime: (dt, offset, save) ->
# Apply the gmt offset to the endTime
# This is the "standard" time; offset but without daylight savings rules applied
endTime = @UTCToStandardTime dt, offset
# Apply the daylight savings to the offset
# Moves the clock forward the amount of time in 'save'
@ApplySave endTime, save
UTCToStandardTime: (dt, offset) ->
# Apply the gmt offset to the endTime because all our dates are represented with UTC underneath
@ApplyOffset dt, offset, true
UTCToQualifiedTime: (dt, qualifier, offset, getSave) ->
endTime = dt
switch qualifier
when "w"
# Wall Time, apply gmt offset then daylight savings
endTime = @UTCToWallTime endTime, offset, getSave()
when "s"
# Standard Time, apply gmt offset only
endTime = @UTCToStandardTime endTime, offset
else
# already in utc time, so nothing to do.
endTime
QualifiedTimeToUTC: (dt, qualifier, offset, getSave) ->
endTime = dt
switch qualifier
when "w"
# Wall Time, apply gmt offset then daylight savings
endTime = @WallTimeToUTC offset, getSave(), endTime
when "s"
# Standard Time, apply gmt offset only
endTime = @StandardTimeToUTC offset, endTime
else
# already in utc time, so nothing to do.
endTime
StandardTimeToUTC: (offset, y, m = 0, d = 1, h = 0, mi = 0, s = 0, ms = 0) ->
dt = if typeof y == "number" then @MakeDateFromParts y, m, d, h, mi, s, ms else y
# Jump up the gmt Offset
@ApplyOffset dt, offset
WallTimeToUTC: (offset, save, y, m = 0, d = 1, h = 0, mi = 0, s = 0, ms = 0) ->
dt = @StandardTimeToUTC offset, y, m, d, h, mi, s, ms
# Jump back the save value
@ApplySave dt, save, true
# Make a date from the passed in parts
MakeDateFromParts: (y, m = 0, d = 1, h = 0, mi = 0, s = 0, ms = 0) ->
if Date.UTC
return new Date(Date.UTC y, m, d, h, mi, s, ms)
# Handle browsers with no Date.UTC
dt = new Date
dt.setUTCFullYear y
dt.setUTCMonth m
dt.setUTCDate d
dt.setUTCHours h
dt.setUTCMinutes mi
dt.setUTCSeconds s
dt.setUTCMilliseconds ms
# return the date
dt
LocalDate: (offset, save, y, m = 0, d = 1, h = 0, mi = 0, s = 0, ms = 0) ->
@WallTimeToUTC offset, save, y, m, d, h, mi, s, ms
# Make a date from a millisecond timestamp
MakeDateFromTimeStamp: (ts) ->
new Date(ts)
MaxDate: ->
# Sun, 26 Jan 29349 00:00:00 GMT
@MakeDateFromTimeStamp 10000000*86400000
MinDate: ->
# Mon, 06 Dec -25410 00:00:00 GMT
@MakeDateFromTimeStamp -10000000*86400000
helpers =
Days: Days
Months: Months
Milliseconds: Milliseconds
Time: Time
noSave:
hours: 0
mins: 0
noZone:
offset:
negative: false
hours: 0
mins: 0
secs: 0
name: "UTC"
if typeof window == 'undefined'
module.exports = helpers
else if typeof define != 'undefined'
define helpers
else
@.WallTime or= {}
@.WallTime.helpers = helpers