github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/cmds/core/date/date.go (about)

     1  // Copyright 2015-2017 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // date prints the date.
     6  //
     7  // Synopsis:
     8  //
     9  //	date [-u] [+format] | date [-u] [MMDDhhmm[CC]YY[.ss]]
    10  package main
    11  
    12  import (
    13  	"flag"
    14  	"fmt"
    15  	"io"
    16  	"log"
    17  	"os"
    18  	"regexp"
    19  	"strconv"
    20  	"strings"
    21  	"time"
    22  )
    23  
    24  type Clock interface {
    25  	Now() time.Time
    26  }
    27  
    28  type RealClock struct{}
    29  
    30  func (r RealClock) Now() time.Time {
    31  	return time.Now()
    32  }
    33  
    34  var (
    35  	// default format map from format.go on time lib
    36  	// Help to make format of date with posix compliant
    37  	fmtMap = map[string]string{
    38  		"%a": "Mon",
    39  		"%A": "Monday",
    40  		"%b": "Jan",
    41  		"%h": "Jan",
    42  		"%B": "January",
    43  		"%c": time.UnixDate,
    44  		"%d": "02",
    45  		"%e": "_2",
    46  		"%H": "15",
    47  		"%I": "03",
    48  		"%m": "1",
    49  		"%M": "04",
    50  		"%p": "PM",
    51  		"%S": "05",
    52  		"%y": "06",
    53  		"%Y": "2006",
    54  		"%z": "-0700",
    55  		"%Z": "MST",
    56  	}
    57  	flags struct {
    58  		universal bool
    59  		reference string
    60  	}
    61  )
    62  
    63  const cmd = "date [-u] [-d FILE] [+format] | date [-u] [-d FILE] [MMDDhhmm[CC]YY[.ss]]"
    64  
    65  func init() {
    66  	defUsage := flag.Usage
    67  	flag.Usage = func() {
    68  		os.Args[0] = cmd
    69  		defUsage()
    70  	}
    71  	flag.BoolVar(&flags.universal, "u", false, "Coordinated Universal Time (UTC)")
    72  	flag.StringVar(&flags.reference, "r", "", "Display the last modification time of FILE")
    73  }
    74  
    75  // regex search for +format POSIX patterns
    76  func formatParser(args string) []string {
    77  	pattern := regexp.MustCompile("%[a-zA-Z]")
    78  	match := pattern.FindAll([]byte(args), -1)
    79  
    80  	var results []string
    81  	for _, m := range match {
    82  		results = append(results, string(m[:]))
    83  	}
    84  
    85  	return results
    86  }
    87  
    88  // replace map for the format patterns according POSIX and GNU implementations
    89  func dateMap(t time.Time, z *time.Location, format string) string {
    90  	d := t.In(z)
    91  	var toReplace string
    92  	for _, match := range formatParser(format) {
    93  		translate, exists := fmtMap[match]
    94  		switch {
    95  		case exists:
    96  			// Values defined by fmtMap
    97  			toReplace = d.Format(translate)
    98  		case match == "%C":
    99  			// Century (a year divided by 100 and truncated to an integer)
   100  			// as a decimal number [00,99].
   101  			toReplace = strconv.Itoa(d.Year() / 100)
   102  		case match == "%D":
   103  			// Date in the format mm/dd/yy.
   104  			toReplace = dateMap(t, z, "%m/%d/%y")
   105  		case match == "%j":
   106  			// Day of the year as a decimal number [001,366]."
   107  			year, weekYear := d.ISOWeek()
   108  			firstWeekDay := time.Date(year, 1, 1, 1, 1, 1, 1, time.UTC).Weekday()
   109  			weekDay := d.Weekday()
   110  			dayYear := int(weekYear)*7 - (int(firstWeekDay) - 1) + int(weekDay)
   111  			toReplace = strconv.Itoa(dayYear)
   112  		case match == "%n":
   113  			// A <newline>.
   114  			toReplace = "\n"
   115  		case match == "%r":
   116  			// 12-hour clock time [01,12] using the AM/PM notation;
   117  			// in the POSIX locale, this shall be equivalent to %I : %M : %S %p.
   118  			toReplace = dateMap(t, z, "%I:%M:%S %p")
   119  		case match == "%t":
   120  			// A <tab>.
   121  			toReplace = "\t"
   122  		case match == "%T":
   123  			toReplace = dateMap(t, z, "%H:%M:%S")
   124  		case match == "%W":
   125  			// Week of the year (Sunday as the first day of the week)
   126  			// as a decimal number [00,53]. All days in a new year preceding
   127  			// the first Sunday shall be considered to be in week 0.
   128  			_, weekYear := d.ISOWeek()
   129  			weekDay := int(d.Weekday())
   130  			isNotSunday := 1
   131  			if weekDay == 0 {
   132  				isNotSunday = 0
   133  			}
   134  			toReplace = strconv.Itoa(weekYear - (isNotSunday))
   135  		case match == "%w":
   136  			// Weekday as a decimal number [0,6] (0=Sunday).
   137  			toReplace = strconv.Itoa(int(d.Weekday()))
   138  		case match == "%V":
   139  			// Week of the year (Monday as the first day of the week)
   140  			// as a decimal number [01,53]. If the week containing January 1
   141  			// has four or more days in the new year, then it shall be
   142  			// considered week 1; otherwise, it shall be the last week
   143  			// of the previous year, and the next week shall be week 1.
   144  			_, weekYear := d.ISOWeek()
   145  			toReplace = strconv.Itoa(int(weekYear))
   146  		case match == "%x":
   147  			// Locale's appropriate date representation.
   148  			toReplace = dateMap(t, z, "%m/%d/%y") // TODO: decision algorithm
   149  		case match == "%F":
   150  			// Date yyyy-mm-dd defined by GNU implementation
   151  			toReplace = dateMap(t, z, "%Y-%m-%d")
   152  		case match == "%X":
   153  			// Locale's appropriate time representation.
   154  			toReplace = dateMap(t, z, "%I:%M:%S %p") // TODO: decision algorithm
   155  		default:
   156  			continue
   157  		}
   158  
   159  		format = strings.Replace(format, match, toReplace, 1)
   160  		// fmt.Printf("Iteration[%d]: %v\n", iter, format)
   161  	}
   162  	return format
   163  }
   164  
   165  func ints(s string, i ...*int) error {
   166  	var err error
   167  	for _, p := range i {
   168  		if *p, err = strconv.Atoi(s[0:2]); err != nil {
   169  			return err
   170  		}
   171  		s = s[2:]
   172  	}
   173  	return nil
   174  }
   175  
   176  // getTime gets the desired time as a time.Time.
   177  // It derives it from a unix date command string.
   178  // Some values in the string are optional, namely
   179  // YY and SS. For these values, we use
   180  // time.Now(). For the timezone, we use whatever
   181  // one we are in, or UTC if desired.
   182  func getTime(z *time.Location, s string, clocksource Clock) (t time.Time, err error) {
   183  	var MM, DD, hh, mm int
   184  	// CC is the year / 100, not the "century".
   185  	// i.e. for 2001, CC is 20, not 21.
   186  	YY := clocksource.Now().Year() % 100
   187  	CC := clocksource.Now().Year() / 100
   188  	SS := clocksource.Now().Second()
   189  	if err = ints(s, &MM, &DD, &hh, &mm); err != nil {
   190  		return
   191  	}
   192  	s = s[8:]
   193  	switch len(s) {
   194  	case 0:
   195  	case 2:
   196  		err = ints(s, &YY)
   197  	case 3:
   198  		err = ints(s[1:], &SS)
   199  	case 4:
   200  		err = ints(s, &CC, &YY)
   201  	case 5:
   202  		s = s[0:2] + s[3:]
   203  		err = ints(s, &YY, &SS)
   204  	case 7:
   205  		s = s[0:4] + s[5:]
   206  		err = ints(s, &CC, &YY, &SS)
   207  	default:
   208  		err = fmt.Errorf("optional string is %v instead of [[CC]YY][.ss]", s)
   209  	}
   210  
   211  	if err != nil {
   212  		return
   213  	}
   214  
   215  	YY = YY + CC*100
   216  	t = time.Date(YY, time.Month(MM), DD, hh, mm, SS, 0, z)
   217  	return
   218  }
   219  
   220  func date(t time.Time, z *time.Location) string {
   221  	return t.In(z).Format(time.UnixDate)
   222  }
   223  
   224  func run(args []string, univ bool, ref string, clocksource Clock, w io.Writer) error {
   225  	t := clocksource.Now()
   226  	z := time.Local
   227  	if univ {
   228  		z = time.UTC
   229  	}
   230  	if ref != "" {
   231  		stat, err := os.Stat(ref)
   232  		if err != nil {
   233  			return fmt.Errorf("unable to gather stats of file %v", ref)
   234  		}
   235  		t = stat.ModTime()
   236  	}
   237  
   238  	switch len(args) {
   239  	case 0:
   240  		fmt.Fprintf(w, "%v\n", date(t, z))
   241  	case 1:
   242  		a0 := args[0]
   243  		if strings.HasPrefix(a0, "+") {
   244  			fmt.Fprintf(w, "%v\n", dateMap(t, z, a0[1:]))
   245  		} else {
   246  			if err := setDate(args[0], z, clocksource); err != nil {
   247  				return fmt.Errorf("%v: %v", a0, err)
   248  			}
   249  		}
   250  	default:
   251  		flag.Usage()
   252  		return nil
   253  	}
   254  	return nil
   255  }
   256  
   257  func main() {
   258  	flag.Parse()
   259  	rc := RealClock{}
   260  	if err := run(flag.Args(), flags.universal, flags.reference, rc, os.Stdout); err != nil {
   261  		log.Fatalf("date: %v", err)
   262  	}
   263  }