github.com/qioalice/ekago/v3@v3.3.2-0.20221202205325-5c262d586ee4/ekatime/date.go (about)

     1  // Copyright © 2020. All rights reserved.
     2  // Author: Ilya Stroy.
     3  // Contacts: iyuryevich@pm.me, https://github.com/qioalice
     4  // License: https://opensource.org/licenses/MIT
     5  
     6  package ekatime
     7  
     8  import (
     9  	"math"
    10  )
    11  
    12  type (
    13  	// Day is a special type that has enough space to store Day's number.
    14  	// Valid values: [1..31].
    15  	Day int8
    16  
    17  	// Days is like time.Duration from std Golang lib,
    18  	// but indicates time's range in passed days.
    19  	Days int16
    20  
    21  	// Month is a special type that has enough space to store Month's number.
    22  	// Valid values: [1..12]. Use predefined constants to make it clear.
    23  	Month int8
    24  
    25  	// Year is a special type that has enough space to store Year's number.
    26  	// Valid values: [1900..4095].
    27  	Year int16
    28  
    29  	// WeekNumber is a special type that has enough space to store week's number.
    30  	// Valid ranges: [0..53]. Depends on WeekNumber's getter.
    31  	WeekNumber int8
    32  
    33  	// Date is a special object that has enough space to store a some Date
    34  	// (including Day, Month, Year) but does it most RAM efficient way
    35  	// taking only 4 bytes.
    36  	//
    37  	// WARNING!
    38  	// DO NOT COMPARE Date OBJECTS JUST BY EQUAL OPERATOR! PREPARE THEM BEFORE.
    39  	//
    40  	//     Because of the internal parts, the Date objects that represents the same
    41  	//     date (e.g: 01 Jan 1970) may not be equal by just eq comparison, like:
    42  	//
    43  	//         d1 := NewTimestamp(NewDate(1970, 1, 1), 0).Date()
    44  	//         d2 := NewDate(1970, 1, 1)
    45  	//         d1 == d2 // false <---
    46  	//
    47  	//     For being able to use eq operator, call ToCmp() before,
    48  	//     or use Equal() method:
    49  	//
    50  	//         d1 := NewTimestamp(NewDate(1970, 1, 1), 0).Date()
    51  	//         d2 := NewDate(1970, 1, 1)
    52  	//         d1.ToCmp() == d2.ToCmp() // true <---
    53  	//         d1.Equal(d2) // true <---
    54  	//
    55  	Date uint32
    56  )
    57  
    58  //noinspection GoSnakeCaseUsage,GoUnusedConst
    59  const (
    60  	MONTH_JANUARY Month = 1 + iota
    61  	MONTH_FEBRUARY
    62  	MONTH_MARCH
    63  	MONTH_APRIL
    64  	MONTH_MAY
    65  	MONTH_JUNE
    66  	MONTH_JULY
    67  	MONTH_AUGUST
    68  	MONTH_SEPTEMBER
    69  	MONTH_OCTOBER
    70  	MONTH_NOVEMBER
    71  	MONTH_DECEMBER
    72  )
    73  
    74  // BelongsToMonth reports whether passed Day belongs to provided Month.
    75  //
    76  // It returns true for 29 Feb, cause there's no leap year check.
    77  // If you want it, use Day.BelongsToMonthAndYear() method.
    78  func (d Day) BelongsToMonth(m Month) bool {
    79  
    80  	// 2004 is a leap year, so leap check will pass.
    81  	// Thus, call Day(29).BelongsToMonth() will always return true.
    82  
    83  	return d.BelongsToMonthAndYear(2004, m)
    84  }
    85  
    86  // BelongsToMonthAndYear reports whether passed Day belongs to provided Month,
    87  // with checking leap year.
    88  func (d Day) BelongsToMonthAndYear(y Year, m Month) bool {
    89  	return (m.IsValid() && d <= _Table0[m-1]) || (d == 29 && m == MONTH_FEBRUARY && y.IsLeap())
    90  }
    91  
    92  // BelongsToYear reports whether current Days object belongs to provided Year
    93  // (has value [1..365/366]).
    94  // The upper bound depends of whether provided year is leap or not.
    95  func (d Days) BelongsToYear(y Year) bool {
    96  	return (1 <= d && d <= 365) || (d == 366 && y.IsLeap())
    97  }
    98  
    99  // IsValid reports whether m is valid month (any of Jan,Feb,...,Dec).
   100  func (m Month) IsValid() bool {
   101  	return MONTH_JANUARY <= m && m <= MONTH_DECEMBER
   102  }
   103  
   104  // DaysInForYear is an alias for DaysInMonth(y, m), where m is the current Month.
   105  func (m Month) DaysInForYear(y Year) Day {
   106  	return DaysInMonth(y, m)
   107  }
   108  
   109  // DaysInIgnoreYear is an alias for DaysInMonthIgnoreYear(m),
   110  // where m is the current Month.
   111  func (m Month) DaysInIgnoreYear() Day {
   112  	return DaysInMonthIgnoreYear(m)
   113  }
   114  
   115  // String returns a full capitalized month name in English.
   116  func (m Month) String() string {
   117  	switch m {
   118  	case MONTH_JANUARY:
   119  		return "January"
   120  	case MONTH_FEBRUARY:
   121  		return "February"
   122  	case MONTH_MARCH:
   123  		return "March"
   124  	case MONTH_APRIL:
   125  		return "April"
   126  	case MONTH_MAY:
   127  		return "May"
   128  	case MONTH_JUNE:
   129  		return "June"
   130  	case MONTH_JULY:
   131  		return "July"
   132  	case MONTH_AUGUST:
   133  		return "August"
   134  	case MONTH_SEPTEMBER:
   135  		return "September"
   136  	case MONTH_OCTOBER:
   137  		return "October"
   138  	case MONTH_NOVEMBER:
   139  		return "November"
   140  	case MONTH_DECEMBER:
   141  		return "December"
   142  	default:
   143  		return "Unknown"
   144  	}
   145  }
   146  
   147  // IsValid reports whether current Year is valid and between [1900..4095] years.
   148  func (y Year) IsValid() bool {
   149  	return _YEAR_MIN <= y && y <= _YEAR_MAX
   150  }
   151  
   152  // IsLeap returns true if the current year is leap (e.g. 1992, 1996, 2000, 2004, etc).
   153  func (y Year) IsLeap() bool {
   154  	return IsLeap(y)
   155  }
   156  
   157  // IsValidDate reports whether 'y', 'm' and 'd' in their valid ranges.
   158  // Also checks leap year, february 28,29, correct day number for month.
   159  func IsValidDate(y Year, m Month, d Day) bool {
   160  	return !(y < 0 || y > 4095 || m < 1 || m > 12 || d < 1 ||
   161  		(IsLeap(y) && m == 2 && d > 29) || d > _Table0[m-1])
   162  }
   163  
   164  // IsValid is an alias for IsValidDate().
   165  func (dd Date) IsValid() bool {
   166  	return IsValidDate(dd.Split())
   167  }
   168  
   169  // ToCmp returns the current Date object ready for being compared using eq operator ==.
   170  // Yes you MUST NOT compare Date objects directly w/o call this method.
   171  // See Date's doc for more details.
   172  func (dd Date) ToCmp() Date {
   173  	return dd &^ (Date(_DATE_MASK_WEEKDAY) << _DATE_OFFSET_WEEKDAY)
   174  }
   175  
   176  // Equal returns true if the current Date is the same as 'other'.
   177  func (dd Date) Equal(other Date) bool {
   178  	return dd.ToCmp() == other.ToCmp()
   179  }
   180  
   181  // Year returns the year number the current Date includes which.
   182  //
   183  // It guarantees that Year() returns the valid year number Date is of,
   184  // only if Date has not been created manually but using constructors
   185  // like NewDate(), Event.Date(), Timestamp.Date(), Timestamp.Split(), etc.
   186  func (dd Date) Year() Year {
   187  	return Year(dd>>_DATE_OFFSET_YEAR) & _DATE_MASK_YEAR
   188  }
   189  
   190  // Month returns the month number the current Date includes which.
   191  // January is 1, not 0.
   192  //
   193  // It guarantees that Month() returns the valid month number Date is of,
   194  // only if Date has not been created manually but using constructors
   195  // like NewDate(), Event.Date(), Timestamp.Date(), Timestamp.Split(), etc.
   196  func (dd Date) Month() Month {
   197  	return Month(dd>>_DATE_OFFSET_MONTH) & _DATE_MASK_MONTH
   198  }
   199  
   200  // Day returns the day number the current Date includes which.
   201  //
   202  // It guarantees that Day() returns the valid day number for the month Date is of,
   203  // only if Date has not been created manually but using constructors
   204  // like NewDate(), Event.Date(), Timestamp.Date(), Timestamp.Split(), etc.
   205  func (dd Date) Day() Day {
   206  	return Day(dd>>_DATE_OFFSET_DAY) & _DATE_MASK_DAY
   207  }
   208  
   209  // DaysInMonth returns how much days the month contains the current Date includes which.
   210  func (dd Date) DaysInMonth() Day {
   211  	y, m, d := normalizeDate(dd.Split())
   212  	d = _Table0[m-1]
   213  	if m == MONTH_FEBRUARY && y.IsLeap() {
   214  		d++
   215  	}
   216  	return d
   217  }
   218  
   219  // DaysInMonth returns how much days requested Month contains in passed Year.
   220  // Returns -1, if Year and Month not in their allowed ranges.
   221  func DaysInMonth(y Year, m Month) Day {
   222  	if m.IsValid() {
   223  		d := _Table0[m-1]
   224  		if m == MONTH_FEBRUARY && y.IsLeap() {
   225  			d++
   226  		}
   227  		return d
   228  	}
   229  	return -1
   230  }
   231  
   232  // DaysInMonthIgnoreYear does the same thing as DaysInMonth,
   233  // but does not require the Year, meaning that it will be always 28 for MONTH_FEBRUARY,
   234  // even for leap years like 2000, 2004, etc.
   235  func DaysInMonthIgnoreYear(m Month) Day {
   236  	if m.IsValid() {
   237  		return _Table0[m-1]
   238  	} else {
   239  		return -1
   240  	}
   241  }
   242  
   243  // Weekday returns the current Date's day of week.
   244  func (dd Date) Weekday() Weekday {
   245  	if w := Weekday(dd>>_DATE_OFFSET_WEEKDAY) & _DATE_MASK_WEEKDAY; w > 0 {
   246  		return w - 1
   247  	} else {
   248  		return dd.WithTime(0, 0, 0).Weekday()
   249  	}
   250  }
   251  
   252  // ISOWeek returns an ISO week of the current Date.
   253  // Returned value will be in the range [1..53].
   254  // Returns -1 if Date is not valid.
   255  //
   256  // Read more:
   257  // https://en.wikipedia.org/wiki/ISO_week_date
   258  func (dd Date) ISOWeek() WeekNumber {
   259  	y, m, d := dd.Split()
   260  	dow := dd.Weekday().To06()
   261  	if dow == 0 {
   262  		dow = 7
   263  	}
   264  	thu := d + Day(4-dow) // nearest thursday
   265  	if m == MONTH_DECEMBER && thu > 31 {
   266  		return 1
   267  	}
   268  	if m == MONTH_JANUARY && thu < 1 {
   269  		y--
   270  		m = MONTH_DECEMBER
   271  		thu += 31
   272  	}
   273  	doy := math.Floor(275*float64(m)/9) + float64(thu-31)
   274  	if m > MONTH_FEBRUARY {
   275  		if IsLeap(y) {
   276  			doy++
   277  		}
   278  		doy -= 2
   279  	}
   280  	return WeekNumber(1 + math.Floor(doy/7))
   281  }
   282  
   283  // Split returns the year number, month number and day number the current Date
   284  // includes which.
   285  // It's just like a separate Year(), Month(), Day() calls.
   286  func (dd Date) Split() (y Year, m Month, d Day) {
   287  	return dd.Year(), dd.Month(), dd.Day()
   288  }
   289  
   290  // NewDate creates a new Date object using provided year number, month number,
   291  // day number, normalizing these values and shifting date if it's required.
   292  //
   293  // Totally, it just stores the provided data if values are in their valid ranges,
   294  // like: [1900..4095] for year, [1..12] for month and [1..X] for day,
   295  // where X depends by month and year.
   296  //
   297  // If they are not, the date may be (will be) shifted. E.g:
   298  // 0 May 2016 (y == 2016, m == 5, d == 0) -> 30 April 2016 (y == 2016, m == 4, d == 30).
   299  func NewDate(y Year, m Month, d Day) Date {
   300  
   301  	if y > 4095 {
   302  		y = 4095
   303  	}
   304  
   305  	y, m, d = normalizeDate(y, m, d)
   306  
   307  	// Do not forgot 'bitwise AND' between y, m, d and their bitmasks
   308  	// if you will change the logic of getting valid y, m, d from invalid ones.
   309  	// Now it's unnecessary (redundant).
   310  	//y, m, d = y & _DATE_MASK_YEAR, m & _DATE_MASK_MONTH, d & _DATE_MASK_DAY
   311  
   312  	return (Date(y) << _DATE_OFFSET_YEAR) |
   313  		(Date(m) << _DATE_OFFSET_MONTH) |
   314  		(Date(d) << _DATE_OFFSET_DAY)
   315  }
   316  
   317  // NewDateFromDayOfYear creates a new Date object using provided year number
   318  // and the day of year on row.
   319  //
   320  // It's allowed to pass < 0 days
   321  // to construct a date less than 1 Jan of your year.
   322  //
   323  // Examples:
   324  //
   325  //  NewDateFromDayOfYear(2021, 1)  // 1 Jan 2021
   326  //  NewDateFromDayOfYear(2021, 2)  // 2 Jan 2021
   327  //  NewDateFromDayOfYear(2021, 33)  // 2 Feb 2021
   328  //  NewDateFromDayOfYear(2021, 254) // 11 Sep 2021
   329  //
   330  func NewDateFromDayOfYear(y Year, days Days) Date {
   331  	return NewDate(y, MONTH_JANUARY, 1).AddDays(days - 1)
   332  }
   333  
   334  // Replace returns a new Date based on the current.
   335  // It returns the current Date with changed Year, Month, Day to those passed values,
   336  // which are in their allowed ranges. Does not doing date addition. Only replacing.
   337  // For date addition, use Add() method.
   338  //
   339  // Examples:
   340  //  NewDate(2021, MONTH_FEBRUARY, 10)    // -> 10 Feb 2021
   341  //    .Replace(2013, MONTH_JANUARY, 2)   // -> 02 Jan 2013
   342  //    .Replace(2020, -2, 4)              // -> 04 Jan 2020
   343  //    .Replace(0, 0, -50)                // -> 04 Jan 2020
   344  //    .Replace(1899, 61, 31)             // -> 31 Jan 2020 (1900 is min year)
   345  //    .Replace(2013, MONTH_FEBRUARY, -2) // -> 31 Jan 2013 (31 Feb is not allowed).
   346  //    .Replace(2014, MONTH_FEBRUARY, 30) // -> 30 Jan 2014
   347  //    .Replace(2000, MONTH_FEBRUARY, 29) // -> 29 Feb 2000
   348  //    .Replace(2001, -1, -1)             // -> 29 Feb 2000 (29 Feb in 2001 is not allowed)
   349  func (dd Date) Replace(y Year, m Month, d Day) Date {
   350  	y_, m_, d_ := dd.Split()
   351  
   352  	// There are some edge cases, that must be handled:
   353  	// 1. dd is 29 Feb Leap's year, changing year is requested, check it.
   354  	// 2. dd is 31 of some month, changing to 30-days month is requested, check it.
   355  
   356  	var (
   357  		ndOK = 1 <= d && d <= 31 && d != d_
   358  		nmOK = MONTH_JANUARY <= m && m <= MONTH_DECEMBER && m != m_
   359  		nyOK = _YEAR_MIN <= y && y <= _YEAR_MAX && y != y_
   360  	)
   361  
   362  	//goland:noinspection GoSnakeCaseUsage
   363  	var (
   364  		Zym   = DaysInMonth(y, m)
   365  		Zy_m  = DaysInMonth(y_, m)
   366  		Zy_m_ = DaysInMonth(y_, m_)
   367  		Zym_  = DaysInMonth(y, m_)
   368  	)
   369  
   370  	//switch {
   371  	//case nmOK && nyOK && ndOK && d <= Zym:
   372  	//	y_, m_, d_ = y, m, d
   373  	//
   374  	//case nmOK && nyOK && ndOK && d <= Zy_m:
   375  	//	m_, d_ = m, d
   376  	//
   377  	//case nmOK && nyOK && ndOK && d <= Zym_:
   378  	//	y_, d_ = y, d
   379  	//
   380  	//case nmOK && nyOK && ndOK && d <= Zy_m_:
   381  	//	d_ = d
   382  	//
   383  	//case nmOK && nyOK && !ndOK && d_ <= Zym:
   384  	//	y_, m_ = y, m
   385  	//
   386  	//case nmOK && nyOK && !ndOK && d_ <= Zy_m:
   387  	//	m_ = m
   388  	//
   389  	//case nmOK && nyOK && !ndOK && d_ <= Zym_:
   390  	//	y_ = y
   391  	//
   392  	//case !nmOK && nyOK && ndOK && d <= Zym_:
   393  	//	y_, d_ = y, d
   394  	//
   395  	//case !nmOK && nyOK && ndOK && d_ <= Zym_:
   396  	//	y_ = y
   397  	//
   398  	//case nmOK && !nyOK && ndOK && d <= Zy_m:
   399  	//	m_, d_ = m, d
   400  	//
   401  	//case nmOK && !nyOK && ndOK && d_ <= Zy_m:
   402  	//	m_ = m
   403  	//
   404  	//case nmOK && !nyOK && !ndOK && d_ <= Zy_m:
   405  	//	m_ = m
   406  	//
   407  	//case !nmOK && nyOK && !ndOK && d_ <= Zym_:
   408  	//	y_ = y
   409  	//
   410  	//case !nmOK && !nyOK && ndOK && d <= Zy_m_:
   411  	//	d_ = d
   412  	//}
   413  
   414  	// Switch above is simplified, using CNDF:
   415  	// https://en.wikipedia.org/wiki/Canonical_normal_form
   416  
   417  	var (
   418  		A = nmOK
   419  		B = nyOK
   420  		C = ndOK
   421  		D = d <= Zym
   422  		E = d <= Zy_m
   423  		F = d <= Zy_m_
   424  		G = d <= Zym_
   425  		H = d_ <= Zym
   426  		I = d_ <= Zy_m
   427  		K = d_ <= Zym_
   428  	)
   429  
   430  	if A && B && (C && (D || G) || H || K) || B && (C && (G || K) || K) {
   431  		y_ = y
   432  	}
   433  	if A && B && (C && (D || E) || H || I) || A && (C && (E || I) || I) {
   434  		m_ = m
   435  	}
   436  	if A && B && C && (D || E || G || F) || C && (B && G || A && E || F) {
   437  		d_ = d
   438  	}
   439  
   440  	return NewDate(y_, m_, d_)
   441  }
   442  
   443  // Add returns a new Date based on the current.
   444  // It returns the current Date with changed Year, Month, Day using passed values
   445  // as their addition's deltas.
   446  //
   447  // Examples:
   448  //  NewDate(2021, MONTH_FEBRUARY, 10) // 10 Feb 2021
   449  //    .Add(1, 2, 3)                   // 13 Apr 2022
   450  //    .Add(0, -1, -13)                // 28 Feb 2022 (0 Mar 2022 -> 28 Feb 2022)
   451  //    .Add(0, 1, 3)                   // 31 Mar 2022
   452  //    .Add(0, 1, 0)                   // 01 May 2022 (31 Apr 2022 (not exist) -> 01 May 2022)
   453  //    .Add(0, 127, 0)                 // 01 Dec 2032 (works OK with potential integer overflow)
   454  func (dd Date) Add(y Year, m Month, d Day) Date {
   455  	y_, m_, d_ := dd.Split()
   456  
   457  	if y > _YEAR_MAX {
   458  		y = _YEAR_MAX
   459  	}
   460  	if y < -(y_ - _YEAR_MIN) {
   461  		y = -(y_ - _YEAR_MIN)
   462  	}
   463  
   464  	y_ += y
   465  	y_ += Year(m / 12)
   466  
   467  	m_ += m % 12
   468  
   469  	if y_ > _YEAR_MAX {
   470  		y_ = _YEAR_MAX
   471  	}
   472  	if y_ < _YEAR_MIN {
   473  		y_ = _YEAR_MIN
   474  	}
   475  
   476  	return NewDate(y_, m_, d_).AddDays(Days(d))
   477  }
   478  
   479  // Sub is the same as Add(-y, -m, -d), but exists to make your life easier,
   480  // when you have a Date, not its parts.
   481  //
   482  // Compare:
   483  //     dd1 := NewDate(...)
   484  //     dd2 := NewDate(...)
   485  //     y2, m2, d2 := dd2.Split()
   486  //     dd1.Add(-y2, -m2, d2) // dd1 - dd2
   487  // or
   488  //     dd1.Sub(dd2.Split())
   489  func (dd Date) Sub(y Year, m Month, d Day) Date {
   490  	return dd.Add(-y, -m, -d)
   491  }
   492  
   493  // DayOfYear returns an accumulated number of days that has been passed since 1 Jan.
   494  // Returns 0 if current Date is not valid.
   495  func (dd Date) DayOfYear() Days {
   496  
   497  	// Zeller's congruence.
   498  	// https://en.wikipedia.org/wiki/Ordinal_date
   499  
   500  	y, m, d := dd.Split()
   501  	if m < MONTH_MARCH {
   502  		m += 12
   503  	}
   504  
   505  	doy := (153*(Days(m)-3)+2)/5 + Days(d)
   506  	if m <= MONTH_DECEMBER {
   507  		doy += 59
   508  		if y.IsLeap() {
   509  			doy += 1
   510  		}
   511  	} else {
   512  		doy -= 306
   513  	}
   514  
   515  	return doy
   516  }
   517  
   518  // AddDays returns a new Date based on the current Date.
   519  // It additions exactly passed days to the current Date and returns a result.
   520  func (dd Date) AddDays(days Days) Date {
   521  	return (dd.WithTime(0, 0, 0) + Timestamp(days)*SECONDS_IN_DAY).Date()
   522  }
   523  
   524  // WithTime returns the current Date with the presented Time's hour, minute, second
   525  // as a new Timestamp object.
   526  func (dd Date) WithTime(hh Hour, mm Minute, ss Second) Timestamp {
   527  	y, m, d := dd.Split()
   528  	return NewTimestamp(y, m, d, hh, mm, ss)
   529  }
   530  
   531  // IsLeap returns true if 'y' Year is leap (e.g. 1992, 1996, 2000, 2004, etc).
   532  func IsLeap(y Year) bool {
   533  	return y%400 == 0 || (y%4 == 0 && y%100 != 0)
   534  }
   535  
   536  // InMonth returns how much seconds month 'm' in year 'y' contains.
   537  //
   538  // Returns it as Timestamp because of easy arithmetic operations,
   539  // but it's NOT A TIMESTAMP!
   540  func InMonth(y Year, m Month) Timestamp {
   541  	y, m, _ = normalizeDate(y, m, 1)
   542  
   543  	sec := _Table1[m-1]
   544  	if m == MONTH_FEBRUARY && IsLeap(y) {
   545  		sec += SECONDS_IN_DAY
   546  	}
   547  
   548  	return sec
   549  }
   550  
   551  // InYear returns how much seconds year 'y' contains.
   552  //
   553  // Returns it as Timestamp because of easy arithmetic operations,
   554  // but it's NOT A TIMESTAMP!
   555  func InYear(y Year) Timestamp {
   556  	if IsLeap(y) {
   557  		return SECONDS_IN_366_YEAR
   558  	} else {
   559  		return SECONDS_IN_365_YEAR
   560  	}
   561  }
   562  
   563  // BeginningOfYear returns the Timestamp of the 'y' year beginning:
   564  // 1 January, 00:00:00 (12:00:00 AM).
   565  //
   566  // It's up to 10 times faster for [1970..N+10] years, where N is current year.
   567  func BeginningOfYear(y Year) Timestamp {
   568  	return getBeginningOfYear(y) // there is no need of normalizing
   569  }
   570  
   571  // EndOfYear returns the Timestamp of the 'y' year end:
   572  // 31 December, 23:59:59 (11:59:59 PM).
   573  //
   574  // It's up to 4.8 times faster for [1970..N+10] years, where N is current year.
   575  func EndOfYear(y Year) Timestamp {
   576  	return getBeginningOfYear(y) + InYear(y) - 1 // there is no need of normalizing
   577  }
   578  
   579  // BeginningAndEndOfYear is like BeginningOfYear() and EndOfYear() calls.
   580  func BeginningAndEndOfYear(y Year) TimestampPair {
   581  	by := getBeginningOfYear(y)                  // there is no need of normalizing
   582  	return TimestampPair{by, by + InYear(y) - 1} // there is no need of normalizing
   583  }
   584  
   585  // BeginningOfMonth returns the Timestamp of the 'm' month beginning in 'y' year:
   586  // 1st day, 00:00:00 (12:00:00 AM).
   587  //
   588  // It's up to 7 times faster for [1970..N+10] years, where N is current year.
   589  func BeginningOfMonth(y Year, m Month) Timestamp {
   590  	return getBeginningOfMonth(y, m) // there is no need of normalizing
   591  }
   592  
   593  // EndOfMonth returns the Timestamp of the 'm' month end in 'y' year:
   594  // 29/30/31 (depends by month) day, 23:59:59 (11:59:59 PM).
   595  //
   596  // It's up to 2.1 times faster for [1970..N+10] years, where N is current year.
   597  func EndOfMonth(y Year, m Month) Timestamp {
   598  	return getBeginningOfMonth(y, m) + InMonth(y, m) - 1 // there is no need of normalizing
   599  }
   600  
   601  // BeginningAndEndOfMonth is like BeginningOfMonth() and EndOfMonth() calls.
   602  func BeginningAndEndOfMonth(y Year, m Month) TimestampPair {
   603  	bm := getBeginningOfMonth(y, m)                  // there is no need of normalizing
   604  	return TimestampPair{bm, bm + InMonth(y, m) - 1} // there is no need of normalizing
   605  }