timezone.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. // Package timezone provides global timezone management for the application.
  2. // Similar to PHP's date_default_timezone_set, this package allows setting
  3. // a global timezone that affects all time.Now() calls.
  4. package timezone
  5. import (
  6. "fmt"
  7. "log"
  8. "time"
  9. )
  10. var (
  11. // location is the global timezone location
  12. location *time.Location
  13. // tzName stores the timezone name for logging/debugging
  14. tzName string
  15. )
  16. // Init initializes the global timezone setting.
  17. // This should be called once at application startup.
  18. // Example timezone values: "Asia/Shanghai", "America/New_York", "UTC"
  19. func Init(tz string) error {
  20. if tz == "" {
  21. tz = "Asia/Shanghai" // Default timezone
  22. }
  23. loc, err := time.LoadLocation(tz)
  24. if err != nil {
  25. return fmt.Errorf("invalid timezone %q: %w", tz, err)
  26. }
  27. // Set the global Go time.Local to our timezone
  28. // This affects time.Now() throughout the application
  29. time.Local = loc
  30. location = loc
  31. tzName = tz
  32. log.Printf("Timezone initialized: %s (UTC offset: %s)", tz, getUTCOffset(loc))
  33. return nil
  34. }
  35. // getUTCOffset returns the current UTC offset for a location
  36. func getUTCOffset(loc *time.Location) string {
  37. _, offset := time.Now().In(loc).Zone()
  38. hours := offset / 3600
  39. minutes := (offset % 3600) / 60
  40. if minutes < 0 {
  41. minutes = -minutes
  42. }
  43. sign := "+"
  44. if hours < 0 {
  45. sign = "-"
  46. hours = -hours
  47. }
  48. return fmt.Sprintf("%s%02d:%02d", sign, hours, minutes)
  49. }
  50. // Now returns the current time in the configured timezone.
  51. // This is equivalent to time.Now() after Init() is called,
  52. // but provided for explicit timezone-aware code.
  53. func Now() time.Time {
  54. if location == nil {
  55. return time.Now()
  56. }
  57. return time.Now().In(location)
  58. }
  59. // Location returns the configured timezone location.
  60. func Location() *time.Location {
  61. if location == nil {
  62. return time.Local
  63. }
  64. return location
  65. }
  66. // Name returns the configured timezone name.
  67. func Name() string {
  68. if tzName == "" {
  69. return "Local"
  70. }
  71. return tzName
  72. }
  73. // UTCOffset returns the current UTC offset of the configured timezone, e.g. "+08:00".
  74. func UTCOffset() string {
  75. return getUTCOffset(Location())
  76. }
  77. // StartOfDay returns the start of the given day (00:00:00) in the configured timezone.
  78. func StartOfDay(t time.Time) time.Time {
  79. loc := Location()
  80. t = t.In(loc)
  81. return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, loc)
  82. }
  83. // Today returns the start of today (00:00:00) in the configured timezone.
  84. func Today() time.Time {
  85. return StartOfDay(Now())
  86. }
  87. // EndOfDay returns the end of the given day (23:59:59.999999999) in the configured timezone.
  88. func EndOfDay(t time.Time) time.Time {
  89. loc := Location()
  90. t = t.In(loc)
  91. return time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 999999999, loc)
  92. }
  93. // StartOfWeek returns the start of the week (Monday 00:00:00) for the given time.
  94. func StartOfWeek(t time.Time) time.Time {
  95. loc := Location()
  96. t = t.In(loc)
  97. weekday := int(t.Weekday())
  98. if weekday == 0 {
  99. weekday = 7 // Sunday is day 7
  100. }
  101. return time.Date(t.Year(), t.Month(), t.Day()-weekday+1, 0, 0, 0, 0, loc)
  102. }
  103. // StartOfMonth returns the start of the month (1st day 00:00:00) for the given time.
  104. func StartOfMonth(t time.Time) time.Time {
  105. loc := Location()
  106. t = t.In(loc)
  107. return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, loc)
  108. }
  109. // ParseInLocation parses a time string in the configured timezone.
  110. func ParseInLocation(layout, value string) (time.Time, error) {
  111. return time.ParseInLocation(layout, value, Location())
  112. }
  113. // ParseInUserLocation parses a time string in the user's timezone.
  114. // If userTZ is empty or invalid, falls back to the configured server timezone.
  115. func ParseInUserLocation(layout, value, userTZ string) (time.Time, error) {
  116. loc := Location() // default to server timezone
  117. if userTZ != "" {
  118. if userLoc, err := time.LoadLocation(userTZ); err == nil {
  119. loc = userLoc
  120. }
  121. }
  122. return time.ParseInLocation(layout, value, loc)
  123. }
  124. // NowInUserLocation returns the current time in the user's timezone.
  125. // If userTZ is empty or invalid, falls back to the configured server timezone.
  126. func NowInUserLocation(userTZ string) time.Time {
  127. if userTZ == "" {
  128. return Now()
  129. }
  130. if userLoc, err := time.LoadLocation(userTZ); err == nil {
  131. return time.Now().In(userLoc)
  132. }
  133. return Now()
  134. }
  135. // StartOfDayInUserLocation returns the start of the given day in the user's timezone.
  136. // If userTZ is empty or invalid, falls back to the configured server timezone.
  137. func StartOfDayInUserLocation(t time.Time, userTZ string) time.Time {
  138. loc := Location()
  139. if userTZ != "" {
  140. if userLoc, err := time.LoadLocation(userTZ); err == nil {
  141. loc = userLoc
  142. }
  143. }
  144. t = t.In(loc)
  145. return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, loc)
  146. }