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

276 lines
5.8 KiB
Go

package datetime
import (
"encoding/binary"
"fmt"
"log"
"net"
"strconv"
"time"
)
const (
obrimDatetimeTypeLocal = "local"
obrimDatetimeTypeTrusted = "trusted"
ObrimDatetimeCodeSuccessRetrieved = "SUCCESS_DATETIME_RETRIEVED"
ObrimDatetimeCodeFailedInvalidType = "FAILED_INVALID_TYPE"
ObrimDatetimeCodeFailedInvalidFormat = "FAILED_INVALID_FORMAT"
ObrimDatetimeCodeFailedTrustedTimeRetrieval = "FAILED_TRUSTED_TIME_RETRIEVAL"
)
const obrimDatetimeTrustedNtpPool = "pool.ntp.org:123"
// ObrimDatetimePayload defines the success payload structure.
type ObrimDatetimePayload struct {
Value string `json:"value"`
}
// ObrimDatetimeResponse defines the utility response structure.
type ObrimDatetimeResponse struct {
Status bool `json:"status"`
Code string `json:"code"`
Payload *ObrimDatetimePayload `json:"payload"`
}
// obrimDatetimePatternMap stores framework-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 date and time.
func ObrimDatetime(
datetimeType string,
config map[string]any,
) map[string]any {
log.Println("[datetime] initialization started")
format, code := obrimDatetimeValidate(datetimeType, config)
if code != "" {
log.Printf("[datetime] validation failed: %s", code)
return obrimDatetimeResponse(
false,
code,
"",
)
}
var datetimeValue time.Time
var err error
switch datetimeType {
case obrimDatetimeTypeLocal:
datetimeValue = obrimDatetimeLocal()
case obrimDatetimeTypeTrusted:
datetimeValue, err = obrimDatetimeTrusted()
if err != nil {
log.Printf(
"[datetime] trusted time retrieval failed: %v",
err,
)
return obrimDatetimeResponse(
false,
ObrimDatetimeCodeFailedTrustedTimeRetrieval,
"",
)
}
}
formattedValue := obrimDatetimeFormatApply(
datetimeValue,
format,
)
log.Println("[datetime] datetime retrieved successfully")
return obrimDatetimeResponse(
true,
ObrimDatetimeCodeSuccessRetrieved,
formattedValue,
)
}
// obrimDatetimeValidate validates utility inputs.
func obrimDatetimeValidate(
datetimeType string,
config map[string]any,
) (string, string) {
switch datetimeType {
case obrimDatetimeTypeLocal:
case obrimDatetimeTypeTrusted:
default:
return "", ObrimDatetimeCodeFailedInvalidType
}
formatValue, exists := config["format"]
if !exists {
return "", ObrimDatetimeCodeFailedInvalidFormat
}
format, ok := formatValue.(string)
if !ok || format == "" {
return "", ObrimDatetimeCodeFailedInvalidFormat
}
if !obrimDatetimePatternValidate(format) {
return "", ObrimDatetimeCodeFailedInvalidFormat
}
return format, ""
}
// obrimDatetimePatternValidate validates supported patterns.
func obrimDatetimePatternValidate(pattern string) bool {
switch pattern {
case "timestampsec":
return true
case "timestampmil":
return true
}
_, exists := obrimDatetimePatternMap[pattern]
return exists
}
// obrimDatetimePatternResolve resolves pattern mappings.
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, _ := obrimDatetimePatternResolve(pattern)
return datetimeValue.Format(layout)
}
// obrimDatetimeResponse builds utility responses.
func obrimDatetimeResponse(
status bool,
code string,
value string,
) map[string]any {
if !status {
return map[string]any{
"status": false,
"code": code,
"payload": nil,
}
}
return map[string]any{
"status": true,
"code": code,
"payload": map[string]any{
"value": value,
},
}
}
// obrimDatetimeLocal retrieves host system time.
func obrimDatetimeLocal() time.Time {
return time.Now()
}
// obrimDatetimeTrusted retrieves trusted NTP time.
func obrimDatetimeTrusted() (time.Time, error) {
return obrimDatetimeTrustedSync()
}
// obrimDatetimeTrustedSync synchronizes trusted time from NTP.
func obrimDatetimeTrustedSync() (time.Time, error) {
connection, err := net.DialTimeout(
"udp",
obrimDatetimeTrustedNtpPool,
5*time.Second,
)
if err != nil {
return time.Time{}, err
}
defer connection.Close()
var 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
}
var response = make([]byte, 48)
if _, err = connection.Read(response); err != nil {
return time.Time{}, err
}
var ntpSeconds = binary.BigEndian.Uint32(
response[40:44],
)
var ntpFraction = binary.BigEndian.Uint32(
response[44:48],
)
const ntpEpochOffset = 2208988800
var unixSeconds = int64(ntpSeconds) - ntpEpochOffset
var nanoseconds = (int64(ntpFraction) * 1e9) >> 32
if unixSeconds < 0 {
return time.Time{}, fmt.Errorf(
"invalid ntp timestamp",
)
}
return time.Unix(
unixSeconds,
nanoseconds,
).UTC(), nil
}