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  }