/* strptime - take a date-time string and turn it into seconds since the epoch. */

/* See Makefile for compilation details. */

/*
   Copyright (C) 2023 Free Software Foundation, Inc.

   This file is part of GNU Bash.
   Bash is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Bash is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <config.h>

#if defined (HAVE_UNISTD_H)
#  include <unistd.h>
#endif

#include "bashtypes.h"
#include "posixtime.h"

#include <stdio.h>

#include "builtins.h"
#include "shell.h"
#include "bashgetopt.h"
#include "common.h"

struct date_modifier
{
  char *shorthand;
  int incr;
};

static struct date_modifier date_time_modifiers[] =
{
  { "now",	0 },
  { "today",	0 },
  { "tomorrow",	24*60*60 },
  { "yesterday", -24*60*60 },
  { "day after tomorrow", 48*60*60 },
  { "two days ago", -48*60*60 },
  { "next week", 7*24*60*60 },
  { "last week", -7*24*60*60 },
  { "the day after tomorrow", 48*60*60 },
  { 0, 0 }
};

static char * const date_time_formats[] =
{
  "%a %b %d %T %Z %Y",		/* Unix date */
  "%a %b %d %T %Y",		/* Wkd Mon DD HH:MM:SS YYYY */
  "%FT%T%z",			/* ISO8601 time YYYY-mm-ddTHH:MM:SSzone */
  "%FT%R%z",			/* ISO8601 time YYYY-mm-ddTHH:MMzone */
  "%G-%m-%dT%T%z",		/* ISO8601 time YYYY-mm-ddTHH:MM:SSzone */
  "%G-%m-%dT%R%z",		/* ISO8601 time YYYY-mm-ddTHH:MMzone */
  "%G-%m-%d",			/* ISO8601 time YYYY-mm-dd */
  /* Can't do 8601 time zone offset with colon or fractions of a second */
  "%a, %d %b %Y %T %Z",		/* RFC822/RFC2822 time */
  "%a, %d %b %Y %T %z",		/* RFC822/RFC2822 time */
  "%D %T",			/* mm/dd/yy HH:MM:SS */
  "%D %R",			/* mm/dd/yy HH:MM */
  "%D %r",			/* mm/dd/yy HH:MM:SS a.m.  */
  "%D %I:%M %p",		/* mm/dd/yy HH:MM p.m.  */
  "%m/%d/%Y %T",		/* mm/dd/YYYY HH:MM:SS */
  "%m/%d/%Y %R",		/* mm/dd/YYYY HH:MM */
  "%m/%d/%Y %r",		/* mm/dd/YYYY HH:MM:SS a.m */
  "%m/%d/%Y %I:%M %p",		/* mm/dd/YYYY HH:MM p.m. */
  "%m-%d-%Y %T",		/* mm-dd-YYYY HH:MM:SS */
  "%m-%d-%Y %R",		/* mm-dd-YYYY HH:MM */
  "%m-%d-%Y %r",		/* mm-dd-YYYY HH:MM:SS a.m. */
  "%m-%d-%Y %I:%M %p",		/* mm-dd-YYYY HH:MM p.m. */
  "%Y/%m/%d %T",		/* YYYY/mm/dd HH:MM:SS */
  "%Y/%m/%d %R",		/* YYYY/mm/dd HH:MM */
  "%Y/%m/%d %r",		/* YYYY/mm/dd hh:MM:SS a.m. */
  "%F %T",			/* YYYY-mm-dd HH:MM:SS */
  "%F %r",			/* YYYY-mm-dd HH:MM:SS p.m. */
  "%F %R",			/* YYYY-mm-dd HH:MM */
  "%F %I:%M %p",		/* YYYY-mm-dd HH:MM a.m. */
  "%F",				/* YYYY-mm-dd ISO8601 time */
  "%T",				/* HH:MM:SS */
  "%H.%M.%S",			/* HH.MM.SS */
  /* From coreutils-9.2 date */
  "%Y-%m-%dT%H:%M:%S%z",	/* ISO8601 time */
  "%Y-%m-%dT%H%z",		/* ISO8601 time */
  "%Y-%m-%dT%H:%M%z",		/* ISO8601 time */
  /* RFC 3339 time */
  "%Y-%m-%d %H:%M:%S%z",	/* RFC 3339 time */
  "%Y-%m-%dT%H:%M:%S%z",	/* RFC 3339 time */
  /* more oddball formats */
  "%m.%d.%Y %T",		/* mm.dd.YYYY HH:MM:SS */
  "%m.%d.%Y %R",		/* mm.dd.YYYY HH:MM */
  "%m.%d.%Y %r",		/* mm.dd.YYYY HH:MM:SS a.m. */
  "%m.%d.%Y %I:%M %p",		/* mm.dd.YYYY HH:MM p.m. */
  "%m/%d/%Y",			/* mm/dd/YYYY */
  "%d %B %Y %T",		/* dd Month YYYY HH:MM:SS */
  "%d %B %Y %R",		/* dd Month YYYY HH:MM */
  "%d %B %Y %r",		/* dd Month YYYY HH:MM:SS a.m. */
  "%d %B %Y %I:%M %p",		/* dd Month YYYY HH:MM p.m. */
  "%d %b %Y %T",		/* dd Mon YYYY HH:MM:SS */
  "%d %b %Y %R",		/* dd Mon YYYY HH:MM */
  "%d %b %Y %r",		/* dd Mon YYYY HH:MM:SS a.m. */
  "%d %b %Y %I:%M %p",		/* dd Mon YYYY HH:MM p.m. */
  "%b %d, %Y %T",		/* Mon dd, YYYY HH:MM:SS */
  "%b %d, %Y %R",		/* Mon dd, YYYY HH:MM */
  "%b %d, %Y %r",		/* Mon dd, YYYY HH:MM:SS a.m. */
  "%b %d, %Y %I:%M %p",		/* Mon dd, YYYY HH:MM p.m. */
  "%m-%b-%Y",			/* dd-Mon-YYYY */
  "%m-%b-%Y %T",		/* dd-Mon-YYYY HH:MM:SS */
  "%m-%b-%Y %R",		/* dd-Mon-YYYY HH:MM */
  "%m-%b-%Y %r",		/* dd-Mon-YYYY HH:MM:SS a.m. */
  "%m-%b-%Y %I:%M %p",		/* dd-Mon-YYYY HH:MM p.m. */
  "%d/%b/%Y:%T %z",		/* NCSA log format dd/Mon/YYYY:HH:MM:SS zone */
  "%d/%b/%Y:%T%z",		/* NCSA log format dd/Mon/YYYY:HH:MM:SSzone */
  /* No delimiters */
  "%Y%m%d %T",			/* YYYYMMDD HH:MM:SS */
  "%Y%m%d %R",			/* YYYYMMDD HH:MM */
  "%Y%m%d %r",			/* YYYYMMDD HH:MM:SS a.m. */
  "%Y%m%d %I:%M %p",		/* YYYYMMDD HH:MM p.m. */
  "%Y%m%d %H:%M:%S%z",		/* YYYYMMDD HH:MM:SSzone */
  "%Y%m%dT%H:%M:%S%z",		/* YYYYMMDDTHH:MM:SSzone */
  "%Y%m%dT%T",			/* YYYYMMDDTHH:MM:SS */
  "%Y%m%dT%R",			/* YYYYMMDDTHH:MM */
  /* Non-US formats */
  "%d-%m-%Y",			/* dd-mm-YYYY */
  "%d-%m-%Y %T",		/* dd-mm-YYYY HH:MM:SS */
  "%d-%m-%Y %R",		/* dd-mm-YYYY HH:MM */
  "%d-%m-%Y %r",		/* dd-mm-YYYY HH:MM:SS a.m. */    
  "%d-%m-%Y %I:%M %p",		/* dd-mm-YYYY HH:MM p.m. */
  "%d/%m/%Y %T",		/* dd/mm/YYYY HH:MM:SS */
  "%d/%m/%Y %R",		/* dd/mm/YYYY HH:MM */
  "%d/%m/%Y %r",		/* dd/mm/YYYY HH:MM:SS a.m. */
  "%d/%m/%Y %I:%M %p",		/* dd/mm/YYYY HH:MM p.m. */
  "%Y-%d-%m %T",		/* YYYY-dd-mm HH:MM:SS */
  "%Y-%d-%m %R",		/* YYYY-dd-mm HH:MM */
  "%d-%m-%Y %T",		/* dd-mm-YYYY HH:MM:SS */
  "%d-%m-%Y %R",		/* dd-mm-YYYY HH:MM */
  "%d-%m-%Y %r",		/* dd-mm-YYYY HH:MM:SS a.m. */
  "%d-%m-%Y %I:%M %p",		/* dd-mm-YYYY HH:MM p.m. */
  "%d.%m.%Y %T",		/* dd.mm.YYYY HH:MM:SS */
  "%d.%m.%Y %R",		/* dd.mm.YYYY HH:MM */
  "%d.%m.%Y %r",		/* dd.mm.YYYY HH:MM:SS a.m. */    
  "%d.%m.%Y %I:%M %p",		/* dd.mm.YYYY HH:MM p.m. */
  0
};

static void
inittime (time_t *clock, struct tm *timeptr)
{
  timeptr = localtime (clock);		/* for now */

  /* but default to midnight */
  timeptr->tm_hour = timeptr->tm_min = timeptr->tm_sec = 0;
  /* and let the system figure out the right DST offset */
  timeptr->tm_isdst = -1;
}

int
strptime_builtin (WORD_LIST *list)
{
  char *s;
  struct tm t, *tm;
  time_t now, secs;
  char *datestr;
  int i;

  if (no_options (list))	/* for now */
    return (EX_USAGE);

  list = loptend;
  if (list == 0)
    {
      builtin_usage ();
     return (EX_USAGE);
    }

  datestr = string_list (list);
  if (datestr == 0 || *datestr == 0)
    return (EXECUTION_SUCCESS);

  now = getnow ();
  secs = -1;
  for (i = 0; date_time_modifiers[i].shorthand; i++)
    {
      if (STREQ (datestr, date_time_modifiers[i].shorthand))
	{
	  secs = now + date_time_modifiers[i].incr;
	  break;
	}
    }

  if (secs == -1)
    {
      /* init struct tm */
      inittime (&now, tm);
      t = *tm;
      for (i = 0; date_time_formats[i]; i++)
        {
	  s = strptime (datestr, date_time_formats[i], &t);
	  if (s == 0)
	    continue;
	  /* skip extra characters at the end for now */
	  secs = mktime (&t);
	  break;
        }
    }

  printf ("%ld\n", secs);    
  return (EXECUTION_SUCCESS);
}

char *strptime_doc[] = {
	"Convert a date-time string to seconds since the epoch.",
	"",
	"Take DATE-TIME, a date-time string, parse it against a set of common",
	"date-time formats. If the string matches one of the formats, convert",
	"it into seconds since the epoch and display the result.",
	(char *)NULL
};

/* The standard structure describing a builtin command.  bash keeps an array
   of these structures.  The flags must include BUILTIN_ENABLED so the
   builtin can be used. */
struct builtin strptime_struct = {
	"strptime",		/* builtin name */
	strptime_builtin,		/* function implementing the builtin */
	BUILTIN_ENABLED,	/* initial flags for builtin */
	strptime_doc,		/* array of long documentation strings. */
	"strptime date-time",	/* usage synopsis; becomes short_doc */
	0			/* reserved for internal use */
};
