github.com/embeddedgo/x@v0.0.6-0.20191217015414-d79a36f562e7/time/zoneinfo.go (about) 1 package time 2 3 type Zone struct { 4 Name string // Abbreviated name ("CET", "CEST"). 5 Offset int // Seconds east of UTC. 6 } 7 8 // DST describes daylight saving time zone. 25 least significant bits of Start 9 // and End contain seconds from begining of the year to the month-weekday-hour 10 // at which the DST starts/ends, assuming that the year is not a leap year and 11 // its first day is Monday. 7 most significant bits of Start and End contain 12 // margin, a number of days that weekdays can be shifted back to do not 13 // introduce new last weekday at end of month or to do not lose first weekday 14 // at beginning of month. Examples of not a leap years whose first day is 15 // Monday: 2018, 2029. 16 type DST struct { 17 Zone *Zone 18 Start uint32 19 End uint32 20 } 21 22 // A Location maps time instants to the zone in use at that time. 23 // This is simplified implementation that does not support historical changes. 24 type Location struct { 25 Name string 26 Zone *Zone 27 DST *DST // Nil if DST not used in location. 28 } 29 30 func (l *Location) String() string { 31 return l.Name 32 } 33 34 var utcLoc = Location{"UTC", &Zone{"UTC", 0}, nil} 35 36 // UTC represents Universal Coordinated Time (UTC). 37 var UTC *Location = &utcLoc 38 39 // Local represents the system's local time zone. 40 var Local = &utcLoc 41 42 // FixedZone returns a Location that always uses 43 // the given zone name and offset (seconds east of UTC). 44 func FixedZone(name string, offset int) *Location { 45 return &Location{Name: name, Zone: &Zone{name, offset}} 46 } 47 48 // Lookup returns information about the time zone in use at an instant in time 49 // expressed as absolute time abs. The returned information gives the name of 50 // the zone (such as "CET"), the offset in seconds east of UTC, the start and 51 // end times bracketing abs when that zone is in effect. If start/end falls on 52 // the previous or next year, the approximate value of start/end is returned. 53 // For now only Date uses these values and works fine with such approximation. 54 func (l *Location) lookup(abs uint64) (name string, offset int, start, end uint64) { 55 if l == nil { 56 l = &utcLoc 57 } 58 if l.DST == nil { 59 return l.Zone.Name, l.Zone.Offset, 0, 1<<64 - 1 60 } 61 62 // This code is similar to the code of absDate. See absDate for better 63 // description of any step. 64 65 // Avoid 64-bit calculations. 66 67 // Second of 400-year cycle. 68 s400 := abs % (daysPer400Years * secondsPerDay) 69 70 // Day of 400-year cycle. 71 d400 := int(s400 / secondsPerDay) 72 73 // Second of day. 74 s := int(s400 - uint64(d400)*secondsPerDay) 75 76 // Day of 100-year cycle. 77 n100 := d400 / daysPer100Years 78 n100 -= n100 >> 2 79 d := d400 - daysPer100Years*n100 80 81 // Day of 4-year cycle. 82 n4 := d / daysPer4Years 83 d -= daysPer4Years * n4 84 85 // Day of year (0 means first day). 86 n := d / 365 87 n -= n >> 2 88 d -= 365 * n 89 90 // Calculate second of year and determine does the year is a leap year. 91 ys := d*secondsPerDay + s 92 isLeap := (n == 4-1 && (n4 != 25-1 || n100 == 4-1)) 93 94 // Weekday of first year day. 95 wday := (d400 - d) % 7 // Zero means Monday. 96 97 // Adjust l.DST.Start and l.DST.End that they describe always the same time 98 // on the same month and the same weakday. 99 dstStart, margin := int(l.DST.Start&0x1FFFFFF), int(l.DST.Start>>25) 100 adj := wday 101 if isLeap && dstStart > (31+28+15)*secondsPerDay { 102 // BUG: dstStart > (31+28+15)*secondsPerDay is simplified condition. 103 // Correct condition should use direction bit of margin (not 104 // implemented) to detect that margin describes first n-th weekday 105 // (Saturday, Sunday) of March or last n-th weekday of March. 106 margin-- 107 } 108 if wday >= margin { 109 adj -= 7 110 } 111 dstStart -= adj * secondsPerDay 112 dstEnd, margin := int(l.DST.End&0x1FFFFFF), int(l.DST.End>>25) 113 adj = wday 114 if isLeap && dstEnd > (31+28+15)*secondsPerDay { 115 // BUG: See above. 116 margin-- 117 } 118 if wday >= margin { 119 adj -= 7 120 } 121 dstEnd -= adj * secondsPerDay 122 123 // Date function wants to classify ambiguous time period as not DST 124 if change := l.DST.Zone.Offset - l.Zone.Offset; change > 0 { 125 dstEnd -= change 126 } else { 127 dstStart += change 128 } 129 130 abs -= uint64(ys) // beginning of year 131 start = abs + uint64(dstStart) // start of DST 132 end = abs + uint64(dstEnd) // end of DST 133 134 if dstStart < dstEnd { 135 if ys < dstStart { 136 return l.Zone.Name, l.Zone.Offset, end - 365*secondsPerDay, start 137 } 138 if dstEnd <= ys { 139 return l.Zone.Name, l.Zone.Offset, end, start + 365*secondsPerDay 140 } 141 return l.DST.Zone.Name, l.DST.Zone.Offset, start, end 142 } 143 if ys < dstEnd { 144 return l.DST.Zone.Name, l.DST.Zone.Offset, start - 365*secondsPerDay, end 145 } 146 if dstStart <= ys { 147 return l.DST.Zone.Name, l.DST.Zone.Offset, start, end + 365*secondsPerDay 148 } 149 150 return l.Zone.Name, l.Zone.Offset, end, start 151 } 152 153 // lookupName returns information about the time zone with 154 // the given name (such as "EST") at the given absolute time 155 // (what the given time of day would be in UTC). 156 func (l *Location) lookupName(name string, abs uint64) (offset int, ok bool) { 157 zones := [2]*Zone{l.Zone, l.DST.Zone} 158 for _, zone := range zones { 159 if zone == nil { 160 break 161 } 162 if zone.Name == name { 163 nam, offset, _, _ := l.lookup(abs - uint64(zone.Offset)) 164 if nam == zone.Name { 165 return offset, true 166 } 167 } 168 } 169 // fall back to an ordinary name match. 170 for _, zone := range zones { 171 if zone == nil { 172 break 173 } 174 if zone.Name == name { 175 return zone.Offset, true 176 } 177 } 178 return 179 }