obrimbasecli/essential/visible/service/helper/datetime/datetime.go

219 lines
5.6 KiB
Go

package datetime
import (
"encoding/binary"
"fmt"
"log"
"net"
"strconv"
"time"
)
const (
obrimDatetimeTypeLocal = "local"
obrimDatetimeTypeTrusted = "trusted"
obrimDatetimeCodeSuccess = "SUCCESS_DATETIME_RETRIEVED"
obrimDatetimeCodeInvalidType = "FAILED_INVALID_TYPE"
obrimDatetimeCodeInvalidFormat = "FAILED_INVALID_FORMAT"
obrimDatetimeCodeTrustedTime = "FAILED_TRUSTED_TIME_RETRIEVAL"
obrimDatetimeCodeInternal = "FAILED_INTERNAL"
obrimDatetimeTrustedNTPPoolServer = "pool.ntp.org:123"
)
// ObrimDatetimePayload represents datetime payload data.
type ObrimDatetimePayload struct {
Value string `json:"value"`
}
// ObrimDatetimeResponse represents the standard utility response.
type ObrimDatetimeResponse struct {
Status bool `json:"status"`
Code string `json:"code"`
Payload *ObrimDatetimePayload `json:"payload"`
}
// obrimDatetimePatternMap stores supported format mappings.
var obrimDatetimePatternMap = map[string]string{
"yyyy-MM-dd": "2006-01-02",
"MMM dd, yyyy": "Jan 02, 2006",
"MMMM dd, yyyy": "January 02, 2006",
"EEE, MMM dd, yyyy": "Mon, Jan 02, 2006",
"EEE, MMMM dd, yyyy": "Mon, January 02, 2006",
"EEEE, MMM dd, yyyy": "Monday, Jan 02, 2006",
"EEEE, MMMM dd, yyyy": "Monday, January 02, 2006",
"HH:mm:ss": "15:04:05",
"hh:mm:ss a": "03:04:05 PM",
"yyyy-MM-dd HH:mm:ss": "2006-01-02 15:04:05",
"yyyy-MM-dd HH:mm:ss z": "2006-01-02 15:04:05 MST",
"yyyy-MM-dd'T'HH:mm:ssXXX": time.RFC3339,
}
// ObrimDatetime retrieves and formats current datetime from a selected source.
func ObrimDatetime(datetimeType string, config map[string]string) ObrimDatetimeResponse {
log.Println("[datetime] initialization started")
if code := obrimDatetimeValidate(datetimeType, config); code != "" {
log.Printf("[datetime] validation failed: %s", code)
return obrimDatetimeResponse(false, code, "")
}
format := config["format"]
var (
currentTime time.Time
err error
)
switch datetimeType {
case obrimDatetimeTypeLocal:
currentTime = obrimDatetimeLocal()
case obrimDatetimeTypeTrusted:
currentTime, err = obrimDatetimeTrusted()
if err != nil {
log.Printf("[datetime] trusted retrieval failed: %v", err)
return obrimDatetimeResponse(false, obrimDatetimeCodeTrustedTime, "")
}
default:
return obrimDatetimeResponse(false, obrimDatetimeCodeInvalidType, "")
}
value := obrimDatetimeFormatApply(currentTime, format)
log.Println("[datetime] retrieval completed successfully")
return obrimDatetimeResponse(true, obrimDatetimeCodeSuccess, value)
}
// obrimDatetimeValidate validates utility inputs.
func obrimDatetimeValidate(datetimeType string, config map[string]string) string {
switch datetimeType {
case obrimDatetimeTypeLocal, obrimDatetimeTypeTrusted:
default:
return obrimDatetimeCodeInvalidType
}
format, exists := config["format"]
if !exists || format == "" {
return obrimDatetimeCodeInvalidFormat
}
if !obrimDatetimePatternValidate(format) {
return obrimDatetimeCodeInvalidFormat
}
return ""
}
// obrimDatetimePatternValidate validates supported format patterns.
func obrimDatetimePatternValidate(pattern string) bool {
_, exists := obrimDatetimePatternMap[pattern]
if exists {
return true
}
switch pattern {
case "timestampsec", "timestampmil":
return true
}
return false
}
// obrimDatetimePatternResolve resolves patterns into layouts.
func obrimDatetimePatternResolve(pattern string) (string, bool) {
layout, exists := obrimDatetimePatternMap[pattern]
return layout, exists
}
// obrimDatetimeFormatApply formats datetime values.
func obrimDatetimeFormatApply(datetimeValue time.Time, pattern string) string {
switch pattern {
case "timestampsec":
return strconv.FormatInt(datetimeValue.Unix(), 10)
case "timestampmil":
return strconv.FormatInt(datetimeValue.UnixMilli(), 10)
}
layout, exists := obrimDatetimePatternResolve(pattern)
if !exists {
return ""
}
return datetimeValue.Format(layout)
}
// obrimDatetimeResponse builds standardized responses.
func obrimDatetimeResponse(status bool, code string, value string) ObrimDatetimeResponse {
if !status {
return ObrimDatetimeResponse{
Status: false,
Code: code,
Payload: nil,
}
}
return ObrimDatetimeResponse{
Status: true,
Code: code,
Payload: &ObrimDatetimePayload{
Value: value,
},
}
}
// obrimDatetimeLocal retrieves local system time.
func obrimDatetimeLocal() time.Time {
return time.Now()
}
// obrimDatetimeTrusted retrieves trusted NTP time.
func obrimDatetimeTrusted() (time.Time, error) {
return obrimDatetimeTrustedSync()
}
// obrimDatetimeTrustedSync synchronizes with framework-managed NTP source.
func obrimDatetimeTrustedSync() (time.Time, error) {
connection, err := net.DialTimeout("udp", obrimDatetimeTrustedNTPPoolServer, 5*time.Second)
if err != nil {
return time.Time{}, err
}
defer connection.Close()
request := make([]byte, 48)
request[0] = 0x1B
if _, err = connection.Write(request); err != nil {
return time.Time{}, err
}
if err = connection.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
return time.Time{}, err
}
response := make([]byte, 48)
if _, err = connection.Read(response); err != nil {
return time.Time{}, err
}
seconds := binary.BigEndian.Uint32(response[40:44])
fraction := binary.BigEndian.Uint32(response[44:48])
const ntpEpochOffset = 2208988800
unixSeconds := int64(seconds) - ntpEpochOffset
nanoseconds := (int64(fraction) * 1e9) >> 32
if unixSeconds < 0 {
return time.Time{}, fmt.Errorf("invalid ntp timestamp")
}
return time.Unix(unixSeconds, nanoseconds).UTC(), nil
}