~psmay/+junk/coffee-clock-face-resolver

« back to all changes in this revision

Viewing changes to clock-face-resolve.coffee

  • Committer: pmay
  • Date: 2013-01-09 19:08:17 UTC
  • Revision ID: pmay@w182vgfs1-20130109190817-dkqzxt78038m4sf5
Initial. Code is based on an earlier test and has not yet been adapted to its current file structure.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
 
 
2
MS_PER_MINUTE = 60 * 1000
 
3
MS_PER_DAY = 24 * 60 * MS_PER_MINUTE
 
4
 
 
5
# Copy the UTC face of a `Date` object to a new object's local face, or vice versa.
 
6
 
 
7
# The special cases ensure that any year is treated as non-rolling; e.g. 99 is
 
8
# 0099 and not 1999. (The fact that the shape of a year is congruent modulo 400
 
9
# years is used to help minimize the necessary changes.)
 
10
 
 
11
# Returns a `Date` object with its UTC face (the face returned by `.getUTC*()`)
 
12
# set to the local face (the face returned by the local methods `.get*()`
 
13
# corresponding to `.getUTC*()`) suggested by the `Date` `from`.
 
14
#
 
15
# The conversion is lossless in this direction.
 
16
dateLocalToUtc = (from) ->
 
17
        y = from.getFullYear()
 
18
        mo = from.getMonth()
 
19
        d = from.getDate()
 
20
        h = from.getHours()
 
21
        min = from.getMinutes()
 
22
        s = from.getSeconds()
 
23
        ms = from.getMilliseconds()
 
24
        to = new Date Date.UTC y, mo, d, h, min, s, ms
 
25
        if to.getUTCFullYear() isnt y
 
26
                to.setTime Date.UTC y + 400, mo, d, h, min, s, ms
 
27
                to.setUTCFullYear y
 
28
        to
 
29
 
 
30
# Returns a `Date` object with its local face set to the UTC face suggested by
 
31
# of the `Date` object `from`.
 
32
#
 
33
# This conversion is generally lossy if the face falls within a daylight/summer
 
34
# time ambiguity; the actual results are dependent on the JavaScript
 
35
# implementation.
 
36
dateUtcToLocalRaw = (from) ->
 
37
        y = from.getUTCFullYear()
 
38
        mo = from.getUTCMonth()
 
39
        d = from.getUTCDate()
 
40
        h = from.getUTCHours()
 
41
        min = from.getUTCMinutes()
 
42
        s = from.getUTCSeconds()
 
43
        ms = from.getUTCMilliseconds()
 
44
        to = new Date y, mo, d, h, min, s, ms
 
45
        if to.getFullYear() isnt y
 
46
                to = new Date y + 400, mo, d, h, min, s, ms
 
47
                to.setFullYear y
 
48
        to
 
49
 
 
50
# `faceMs` is an interpretation of an ms date as if the epoch were not
 
51
# strictly UTC but entirely timezone-ignorant instead (i.e., the number of ms
 
52
# since `1970-01-01T00:00:00` with no specified timezone). If a `Date` object
 
53
# is set to `faceMs`, the intended face appears on its UTC face.
 
54
 
 
55
# Returns a `Date` object with the local face set as suggested by `faceMs`. 
 
56
#
 
57
# This conversion is generally lossy if the face falls within a daylight/summer
 
58
# time ambiguity; the actual results are dependent on the JavaScript
 
59
# implementation.
 
60
faceMsToLocalRaw = (faceMs) -> dateUtcToLocalRaw new Date faceMs
 
61
 
 
62
 
 
63
# Returns a `Date` object with its local face set to the UTC face suggested by
 
64
# of the `Date` object `from`. This conversion is generally lossy if the face
 
65
# falls within a daylight/summer time ambiguity; such times are adjusted
 
66
# according to the following rules:
 
67
#
 
68
# - If the face date is skipped by a DST skip forward, the time is resolved to
 
69
#   the first minute following the skip (e.g., if 01:59 is directly followed by
 
70
#   03:00, 02:30 would be mapped to 03:00).
 
71
#       - If two events are scheduled at different times, the event scheduled
 
72
#         earlier will occur no later than the event scheduled later. If one or
 
73
#         both events are scheduled for a skipped time, they may occur at the same
 
74
#         time.
 
75
# - If the face date happens twice due a DST skip backward, the time is
 
76
#   resolved to the earlier instance (e.g. if 01:00-01:59 daylight time is
 
77
#   directly followed by 01:00-01:59 standard time, 01:30 would be mapped to
 
78
#   01:30 daylight time).
 
79
#       - An event scheduled for a time that is doubled will still occur only once,
 
80
#         at the earlier instance of the time.
 
81
#       - A face cannot be resolved to the later instance of a doubled time;
 
82
#         specifically, even if the target application has a concept of *current
 
83
#         time* and the later instance of the time is in the future but the earlier
 
84
#         instance is in the past, the time itself is considered past.
 
85
 
 
86
dateUtcToLocalAdjusted = (dateWithUtcFace) ->
 
87
        faceMs = dateWithUtcFace.getTime()
 
88
        date = dateUtcToLocalRaw dateWithUtcFace
 
89
        dateOffset = date.getTimezoneOffset()
 
90
 
 
91
        previousDateOffset = (faceMsToLocalRaw faceMs - MS_PER_DAY).getTimezoneOffset()
 
92
        
 
93
        if faceMs is dateLocalToUtc(date).getTime()
 
94
                # Face matches
 
95
                if dateOffset is previousDateOffset
 
96
                        # Matches earliest (or only) instance.
 
97
                        date
 
98
                else
 
99
                        # If there are two instances, matches the later one and needs adjustment.
 
100
                        # Otherwise, matches the only instance.
 
101
                        offsetChangeMinutes = dateOffset - previousDateOffset
 
102
                        if offsetChangeMinutes <= 0
 
103
                                # This is a skipping rather than a doubling adjustment; the
 
104
                                # offset change is the wrong direction
 
105
                                date
 
106
                        else
 
107
                                # The offset change is subtracted from the date's epoch time
 
108
                                # rather than its face.
 
109
                                adjustedDate = new Date date.getTime() - (offsetChangeMinutes * MS_PER_MINUTE)
 
110
 
 
111
                                # If the offsets match, the recent past contains no discontinuity,
 
112
                                # so there is only one instance. Otherwise, adjust the time to be
 
113
                                # before the discontinuity.
 
114
                                if adjustedDate.getTimezoneOffset() is dateOffset then date else adjustedDate
 
115
        else
 
116
                # Face refers to a skipped time. Resolve to the first minute the end of
 
117
                # the discontinuity. The discontinuity is located by binary search;
 
118
                # minutes are differentiated based on whether a given time's local
 
119
                # offset matches the previous day's offset (before the discontinuity)
 
120
                # or not (after the discontinuity).
 
121
                followingDateOffset = (faceMsToLocalRaw faceMs + MS_PER_DAY).getTimezoneOffset()
 
122
                offsetChangeMinutes = previousDateOffset - followingDateOffset
 
123
                faceMinute = faceMs / MS_PER_MINUTE
 
124
                before = Math.floor( faceMinute - offsetChangeMinutes )
 
125
                after = Math.ceil( faceMinute + offsetChangeMinutes )
 
126
                while before + 1 < after
 
127
                        mid = Math.floor( (before + after) / 2 )
 
128
                        midOffset = (faceMsToLocalRaw mid * MS_PER_MINUTE).getTimezoneOffset()
 
129
                        if midOffset is previousDateOffset
 
130
                                before = mid
 
131
                        else
 
132
                                after = mid
 
133
                faceMsToLocalRaw after * MS_PER_MINUTE