Skip to content

Commit

Permalink
Merge pull request #21 from jftuga/add-unix-time
Browse files Browse the repository at this point in the history
let fmt convert to/from unix (epoch) time
  • Loading branch information
jftuga authored Nov 18, 2024
2 parents 3dd3eff + 81c8b9a commit 7118d1d
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 7 deletions.
74 changes: 68 additions & 6 deletions DateTimeMate.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package DateTimeMate

import (
"fmt"
"strconv"
"strings"
"time"

"github.com/golang-module/carbon/v2"
"github.com/lestrrat-go/strftime"
"github.com/tkuchiki/parsetime"
"strings"
)

const (
ModName string = "DateTimeMate"
ModVersion string = "1.3.0"
ModVersion string = "1.3.1"
ModUrl string = "https://github.com/jftuga/DateTimeMate"
)

Expand Down Expand Up @@ -73,11 +77,43 @@ func removeTrailingS(s string) string {
return strings.TrimSuffix(s, "s")
}

// Reformat the source string to match the strftime outputFormat
// Ex: "2024-07-22 08:21:44", "%v %r" => "22-Jul-2024 08:21:44 AM"
// Reformat converts a date/time string into a specified format. The source can be:
// - A Unix timestamp (e.g., "1700265600")
// - A relative date (e.g., "yesterday", "now")
// - Any other date format parseable by parsetime
//
// The outputFormat parameter uses strftime format specifiers, with additional
// support for Unix seconds via '%s'.
//
// Example usage:
//
// s, err := Reformat("1700265600", "%Y-%m-%d") // Unix timestamp to date
// s, err := Reformat("yesterday", "%Y-%m-%d %H:%M:%S") // Relative date to datetime
// s, err := Reformat("2024-01-01", "%s") // Date to Unix timestamp
//
// Returns an error if:
// - The outputFormat is invalid
// - The source date cannot be parsed
// - The time parser initialization fails
func Reformat(source string, outputFormat string) (string, error) {
source = ConvertRelativeDateToActual(source)
f, err := strftime.New(outputFormat)
source = strings.TrimSpace(source)
if isPureIntegerAtoi(source) {
if source[0] == '-' {
return "", fmt.Errorf("timestamps can't be negative: %v", source)
}
t, err := unixStringToTime(source)
if err != nil {
return "", err
}
source = t.String()
} else {
source = ConvertRelativeDateToActual(source)
}

// creates a new Strftime instance
// outputFormat is a pattern string that follows strftime formatting
// the additional formatting behavior allows this to also use the unix time %s modifier
f, err := strftime.New(outputFormat, strftime.WithUnixSeconds('s'))
if err != nil {
return "", err
}
Expand All @@ -92,3 +128,29 @@ func Reformat(source string, outputFormat string) (string, error) {
}
return f.FormatString(s), nil
}

// unixStringToTime converts a string containing a Unix timestamp to time.Time.
// It accepts timestamps in both seconds (10 digits) and milliseconds (13 digits).
// Returns the corresponding time.Time and any error encountered during conversion.
//
// If the input string is not a valid integer or is empty,
// it returns a zero time.Time and an error.
func unixStringToTime(timestamp string) (time.Time, error) {
unixTime, err := strconv.ParseInt(strings.TrimSpace(timestamp), 10, 64)
if err != nil {
return time.Time{}, err
}

if len(timestamp) == 13 {
return time.UnixMilli(unixTime), nil
}

return time.Unix(unixTime, 0), nil
}

// isPureIntegerAtoi reports whether a string contains a valid base-10 integer.
// It returns true only if the string can be fully converted to an integer.
func isPureIntegerAtoi(s string) bool {
_, err := strconv.Atoi(s)
return err == nil
}
15 changes: 15 additions & 0 deletions DateTimeMate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,19 @@ func TestFormatCommand(t *testing.T) {
fmt = "%Z %z"
correct = "EDT -0400"
testFormat(t, source, fmt, correct)

source = "2024-11-16 14:01:02"
fmt = "%s"
correct = "1731783662"
testFormat(t, source, fmt, correct)

source = "1704085262"
fmt = "%F %T"
correct = "2024-01-01 00:01:02"
testFormat(t, source, fmt, correct)

source = "1704085262999"
fmt = "%F %T"
correct = "2024-01-01 00:01:02"
testFormat(t, source, fmt, correct)
}
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,18 @@ UTC

$ dtmate fmt "Mon Jul 22 08:40:33 EDT 2024" "%Z %z"
EDT -0400

# convert to unix (epoch) time seconds
$ dtmate fmt "2024-11-16 14:01:02" "%s"
1731783662

# from unix (epoch) time seconds
$ dtmate fmt 1704085262 "%F %T"
2024-01-01 00:01:02

# also from milliseconds
$ dtmate fmt 1704085262999 "%F %T"
2024-01-01 00:01:02
```
</details>

Expand Down
14 changes: 13 additions & 1 deletion cmd/dtmate/cmd/fmt.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
package cmd

import (
"errors"
"fmt"
"os"

"github.com/jftuga/DateTimeMate"
"github.com/spf13/cobra"
"os"
)

var fmtCmd = &cobra.Command{
Use: "fmt [date/time] [format specifiers]",
Short: "reformat a date/time",
Args: func(cmd *cobra.Command, args []string) error {
if optFmtList {
return nil
}
if len(args) != 2 {
return errors.New("requires two arguments: [date/time] [format specifiers]")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
if optFmtList {
listConversionsSpecifiers()
Expand Down Expand Up @@ -53,6 +64,7 @@ func listConversionsSpecifiers() {
{`%R`, `equivalent to %H:%M`},
{`%r`, `equivalent to %I:%M:%S %p`},
{`%S`, `the second as a decimal number (00-60)`},
{`%s`, `the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC)`},
{`%T`, `equivalent to %H:%M:%S`},
{`%t`, `a tab`},
{`%U`, `the week number of the year (Sunday as the first day of the week) as a decimal number (00-53)`},
Expand Down

0 comments on commit 7118d1d

Please sign in to comment.