Return local beginning of day time object - datetime

To get a local beginning of today time object I extract YMD and reconstruct the new date. That looks like a kludge. Do I miss some other standard library function?
code also runnable at http://play.golang.org/p/OSRl0nxyB7 :
func Bod(t time.Time) time.Time {
year, month, day := t.Date()
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
}
func main() {
fmt.Println(Bod(time.Now()))
}

Both the title and the text of the question asked for "a local [Chicago] beginning of today time." The Bod function in the question did that correctly. The accepted Truncate function claims to be a better solution, but it returns a different result; it doesn't return a local [Chicago] beginning of today time. For example,
package main
import (
"fmt"
"time"
)
func Bod(t time.Time) time.Time {
year, month, day := t.Date()
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
}
func Truncate(t time.Time) time.Time {
return t.Truncate(24 * time.Hour)
}
func main() {
chicago, err := time.LoadLocation("America/Chicago")
if err != nil {
fmt.Println(err)
return
}
now := time.Now().In(chicago)
fmt.Println(Bod(now))
fmt.Println(Truncate(now))
}
Output:
2014-08-11 00:00:00 -0400 EDT
2014-08-11 20:00:00 -0400 EDT
The time.Truncate method truncates UTC time.
The accepted Truncate function also assumes that there are 24 hours in a day. Chicago has 23, 24, or 25 hours in a day.

EDIT: This only works for UTC times (it was tested in the playground, so the location-specific test was probably wrong). See PeterSO's answer for issues of this solution in location-specific scenarios.
You can use the Truncate method on the date, with 24 * time.Hour as duration:
http://play.golang.org/p/zJ8s9-6Pck
func main() {
// Test with a location works fine too
loc, _ := time.LoadLocation("Europe/Berlin")
t1, _ := time.ParseInLocation("2006 Jan 02 15:04:05 (MST)", "2012 Dec 07 03:15:30 (CEST)", loc)
t2, _ := time.Parse("2006 Jan 02 15:04:05", "2012 Dec 07 00:00:00")
t3, _ := time.Parse("2006 Jan 02 15:04:05", "2012 Dec 07 23:15:30")
t4, _ := time.Parse("2006 Jan 02 15:04:05", "2012 Dec 07 23:59:59")
t5, _ := time.Parse("2006 Jan 02 15:04:05", "2012 Dec 08 00:00:01")
times := []time.Time{t1, t2, t3, t4, t5}
for _, d := range times {
fmt.Printf("%s\n", d.Truncate(24*time.Hour))
}
}
To add some explanation, it works because truncate "rounds down to a multiple of" the specified duration since the zero time, and the zero time is January 1, year 1, 00:00:00. So truncating to the nearest 24-hour boundary always returns a "beginning of day".

Related

How to parse CCYY-MM-DDThh:mm:ss[.sss...] date format

As we all know, date parsing in Go has it's quirks*.
However, I have now come up against needing to parse a datetime string in CCYY-MM-DDThh:mm:ss[.sss...] to a valid date in Go.
This CCYY format is a format that seems to be ubiquitous in astronomy, essentially the CC is the current century, so although we're in 2022, the century is the 21st century, meaning the date in CCYY format would be 2122.
How do I parse a date string in this format, when we can't specify a coded layout?
Should I just parse in that format, and subtract one "century" e.g., 2106 becomes 2006 in the parsed datetime...?
Has anyone come up against this niche problem before?
*(I for one would never have been able to remember January 2nd, 3:04:05 PM of 2006, UTC-0700 if it wasn't the exact time of my birth! I got lucky)
The time package does not support parsing centuries. You have to handle it yourself.
Also note that a simple subtraction is not enough, as e.g. the 21st century takes place between January 1, 2001 and December 31, 2100 (the year may start with 20 or 21). If the year ends with 00, you do not have to subtract 100 years.
I would write a helper function to parse such dates:
func parse(s string) (t time.Time, err error) {
t, err = time.Parse("2006-01-02T15:04:05[.000]", s)
if err == nil && t.Year()%100 != 0 {
t = t.AddDate(-100, 0, 0)
}
return
}
Testing it:
fmt.Println(parse("2101-12-31T12:13:14[.123]"))
fmt.Println(parse("2122-10-29T12:13:14[.123]"))
fmt.Println(parse("2100-12-31T12:13:14[.123]"))
fmt.Println(parse("2201-12-31T12:13:14[.123]"))
Which outputs (try it on the Go Playground):
2001-12-31 12:13:14.123 +0000 UTC <nil>
2022-10-29 12:13:14.123 +0000 UTC <nil>
2100-12-31 12:13:14.123 +0000 UTC <nil>
2101-12-31 12:13:14.123 +0000 UTC <nil>
As for remembering the layout's time:
January 2, 15:04:05, 2006 (zone: -0700) is a common order in the US, and in this representation parts are in increasing numerical order: January is month 1, 15 hour is 3PM, year 2006 is 6. So the ordinals are 1, 2, 3, 4, 5, 6, 7.
I for one would never have been able to remember January 2nd, 3:04:05 PM of 2006, UTC-0700 if it wasn't the exact time of my birth! I got lucky.
The reason for the Go time package layout is that it is derived from the Unix (and Unix-like) date command format. For example, on Linux,
$ date
Fri Apr 15 08:20:43 AM EDT 2022
$
Now, count from left to right,
Month = 1
Day = 2
Hour = 3 (or 15 = 12 + 3)
Minute = 4
Second = 5
Year = 6
Note: Rob Pike is an author of The Unix Programming Environment

How to handle leap years in Ada

I'm supposed to do a program that handles the dates of a year. I'm having a problem with handling the leap year in a good way. I'm not going to post the entire code because it'll be too much. My program runs but I don't like how my last if statement looks.
Information about months and leap years:
Month: Jan Feb Mar Apr May Jun Jul Aug Sep Okt Nov Dec
Days : 31 28/29 31 30 31 30 31 31 30 31 30 30
A year is a leap year if it's evenly dividable by 4, but not even when divided by 100. Years are also evenly dividable by 400 leap years. If it's a leap year, February has 29 days instead of 28.
This is my code (only the part I need help with):
Get(Days);
if Days > 31 or Day < 1 then
raise Day_Error;
elsif Day = 31 and (Month = 2 or Month = 4 or Month = 6 or Month = 9 or Month = 11) then
raise Day_Error;
elsif Day = 30 and (Month = 1 or Month = 3 or Month = 5 or Month = 7 or
Month = 8 or Month = 10 or Month = 12) then
raise Day_Error;
end if;
if Day = 29 and Month = 2 then
if Year mod 400 = 0 or (Year mod 4 = 0 and Year mod 100 /= 0) then
null;
else
raise Day_Error;
end if;
end if;
exit;
end;
end loop;
end Day_Procedure;
My concern is the if statement where I put Day = 29 and Month = 2, can this be solved without having the need to put two if statements?
I think a lot can be gained by putting certain tests in subroutines, even if they are only called once. In the code below I have a routine GetMaxDayInMonth(Month, Year) that gives the maximum day for the given month in the given year. The year is necessary only in the case of February, where the maximum allowed day of the month depends on whether it is in a leap year.
For all other months the return value is constant, which I have recorded in a const array MaxDaysInMonth. Only days in February must be treated specially. That treatment has again been stowed away in its own little subroutine GetFebMaxDay(Day, Month), making everything neat and intelligible.
I have used the new Ada equivalent to the C ternary operator cond?a:b, (if cond then a else b):
function GetFebMaxDay(Year: YearT) return DayInMonthT is
begin
return (if IsLeapYear(Year) then 29 else 28);
end GetFebMaxDay;
The test for valid day then is thus reduced to a single function call
Date.Day <= GetMaxDayInMonth(Date.Month, Date.Year);
I have also created distinct types for year, month and day, imposing constraints where possible (max day in month is 31, after all). Many mistakes would have been avoided if programmers had put feet and meters in their own subtypes that need explicit conversion.
Here is the entire program that checks a few borderline dates:
with Ada.Text_IO; use Ada.Text_IO;
procedure DateCheck is
subtype DayInMonthT is Integer range 1..31;
type MonthT is (JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC);
subtype YearT is Integer;
type DateT is record Year: YearT; Month: MonthT; Day: DayInMonthT; end record;
function IsLeapYear(Y: YearT) return Boolean is
begin
return (Y mod 4 = 0 and Y mod 100 /= 0)
or Y mod 400 = 0;
end IsLeapYear;
function GetFebMaxDay(Year: YearT) return DayInMonthT is
begin
return (if IsLeapYear(Year) then 29 else 28);
end GetFebMaxDay;
function GetMaxDayInMonth(Month: MonthT; Year: YearT) return DayInMonthT is
MaxDaysInMonth: constant array(MonthT) of DayInMonthT :=
( JAN => 31,
FEB => 29,
MAR => 31,
APR => 30,
MAY => 31,
JUN => 30,
JUL => 31,
AUG => 31,
SEP => 30,
OCT => 31,
NOV => 30,
DEC => 31 );
begin return (if Month = FEB then GetFebMaxDay(Year) else MaxDaysInMonth(Month)); end;
function IsValidateDate(Date: DateT) return Boolean is
begin
return Date.Day <= GetMaxDayInMonth(Date.Month, Date.Year);
end;
Dates: array (1..6) of DateT := (others => (0, JAN, 1));
begin
Dates(1) := (2021, FEB, 28);
Dates(2) := (2021, FEB, 29);
Dates(3) := (2021, FEB, 30);
Dates(4) := (2000, FEB, 31);
-- Dates(4) := (2000, FEB, 32); -- raises warning, fails with constraint error
Dates(5) := (2000, FEB, 29);
Dates(6) := (1900, FEB, 29);
for i in Dates'Range loop
Put("Date ");
Put(Dates(i).Year'Image);
Put("-");
Put(Dates(i).Month'Image);
Put("-");
Put(Dates(i).Day'Image);
Put(": Valid date? ");
Put_Line(IsValidateDate(Dates(i))'Image);
end loop;
end DateCheck;
Sample session:
$ gnatmake -gnatwa datecheck.adb && ./datecheck.exe
gcc -c -gnatwa datecheck.adb
gnatbind -x datecheck.ali
gnatlink datecheck.ali
Date 2021-FEB- 28: Valid date? TRUE
Date 2021-FEB- 29: Valid date? FALSE
Date 2021-FEB- 30: Valid date? FALSE
Date 2000-FEB- 31: Valid date? FALSE
Date 2000-FEB- 29: Valid date? TRUE
Date 1900-FEB- 29: Valid date? FALSE

How to parse a timestamp used by PRTG

I have a datetime string this format
44340.5416666667 but i want to convert this to 5/24/2021 3:00:00 PM - 4:00:00 PM format. How can i parse that with golang? I tried some convert function but it didn't work.
According to https://kb.paessler.com/en/topic/1313-how-do-i-translate-prtg-timestamp-values-format-to-normal-time-format, the timestamp format used by PRTG seems to be defined as the value of days since Dec 30, 1899.
Following the above link, the following Go code should convert the timestamp into a Go Time instance:
prtg := 44340.5416666667
// substract number of days between Dec 30, 1899 and Jan 1, 1970 and convert to millis
millis := int64((prtg - 25569) * 86400 * 1000)
t := time.Unix(0, millis*int64(time.Millisecond))
println(t.Format("1/2/2006 03:04:05 PM"))
According to prtg timestamp mentioned in Gregor Zurowski's comment,
convert your time to nano seconds (minimum unit in time to more accurate) and add unix nano of 1899-12-30 12.00 midnight.
re convert it to time and format it as below
package main
import (
"fmt"
"time"
)
func main() {
startDate := time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC).UnixNano()
timeVar := 44340.5416666667 //your time variable
duration := startDate + int64(float64(24*60*60) * timeVar * 1e9) //duration since start date in nanoseconds
fmt.Println(time.Unix(0, duration).Format("1/2/2006 03:04:05 PM"))
}

Convert UTC string to time object

I have this datetime, or something that looks like it.
2014-11-17 23:02:03 +0000 UTC
I want to convert this to a time object and I've been unable to produce any output from time.Parse apart from:
0001-01-01 00:00:00 +0000 UTC
I've tried these layouts:
time.RFC3339
0001-01-01 00:00:00 0000 UTC
2016-10-10
time.UnixDate
And a few more - none have worked.
This is how I'm calling parse :
updatedAt, err := time.Parse(time.UnixDate, updatedAtVar)
How do I create a time object from a string?
Most likely you used a wrong layout, and you didn't check the returned error.
The layout must be this date/time, in the format your input time is:
Mon Jan 2 15:04:05 -0700 MST 2006
See this working code:
layout := "2006-01-02 15:04:05 -0700 MST"
t, err := time.Parse(layout, "2014-11-17 23:02:03 +0000 UTC")
fmt.Println(t, err)
Output (try it on the Go Playground):
2014-11-17 23:02:03 +0000 UTC <nil>
EDIT:
In your question you included a + sign in your input time (as part of the zone offset), but you have error with times of other formats.
Time.String() uses the following format string:
"2006-01-02 15:04:05.999999999 -0700 MST"
So either use this to parse the times, or use Time.Format() to produce your string representations where you can specify the layout, so you can use the same layout to parse the time strings.
2nd round:
You're including your time strings in URLs. The + sign is a special character in URL encoding: it denotes the space. So the + gets converted to space (and so it vanishes from your time string). Use proper URL encoding! Check out the net/url package, and this example.
Didn't see this yet but for those that don't know the formats, time has the formats builtin as constants. so you can reference them when parsing or formating.
time.Parse(time.RFC3339, <your time.Time object here>)
<time.Time object>.Format(time.RFC3339) //or other type of formats
Here they are for reference
ANSIC = "Mon Jan _2 15:04:05 2006"
UnixDate = "Mon Jan _2 15:04:05 MST 2006"
RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
RFC822 = "02 Jan 06 15:04 MST"
RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
RFC3339 = "2006-01-02T15:04:05Z07:00"
RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
Kitchen = "3:04PM"
You are likely using the wrong layout. As explained in time.Parse, you need to specify a layout that helps Go to understand how the date passed as input is formatted.
There are predefined layouts (like the ones you were using), but none matches your input. Hence you need to define a custom layout.
A layout uses the following date as reference:
Mon Jan 2 15:04:05 MST 2006
The layout is nothing else that a representation of that date, that matches the representation of your input:
t, err := time.Parse("2006-01-02 15:04:05 -0700 MST", "2014-11-17 23:02:03 +0000 UTC")
Also remember to check err for errors. It's likely your attempts returned an error, but you didn't check it.
package main
import (
"fmt"
"log"
"time"
)
func main() {
t, err := time.Parse("2006-01-02 15:04:05 -0700 UTC", "2014-11-17 23:02:03 +0000 UTC")
if err != nil {
log.Fatalln(err)
}
fmt.Println(t)
}

Subtracting time.Duration from time in Go

I have a time.Time value obtained from time.Now() and I want to get another time which is exactly 1 month ago.
I know subtracting is possible with time.Sub() (which wants another time.Time), but that will result in a time.Duration and I need it the other way around.
In response to Thomas Browne's comment, because lnmx's answer only works for subtracting a date, here is a modification of his code that works for subtracting time from a time.Time type.
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println("now:", now)
count := 10
then := now.Add(time.Duration(-count) * time.Minute)
// if we had fix number of units to subtract, we can use following line instead fo above 2 lines. It does type convertion automatically.
// then := now.Add(-10 * time.Minute)
fmt.Println("10 minutes ago:", then)
}
Produces:
now: 2009-11-10 23:00:00 +0000 UTC
10 minutes ago: 2009-11-10 22:50:00 +0000 UTC
Not to mention, you can also use time.Hour or time.Second instead of time.Minute as per your needs.
Playground: https://play.golang.org/p/DzzH4SA3izp
Try AddDate:
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println("now:", now)
then := now.AddDate(0, -1, 0)
fmt.Println("then:", then)
}
Produces:
now: 2009-11-10 23:00:00 +0000 UTC
then: 2009-10-10 23:00:00 +0000 UTC
Playground: http://play.golang.org/p/QChq02kisT
You can negate a time.Duration:
then := now.Add(- dur)
You can even compare a time.Duration against 0:
if dur > 0 {
dur = - dur
}
then := now.Add(dur)
You can see a working example at http://play.golang.org/p/ml7svlL4eW
There's time.ParseDuration which will happily accept negative durations, as per manual. Otherwise put, there's no need to negate a duration where you can get an exact duration in the first place.
E.g. when you need to substract an hour and a half, you can do that like so:
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println("now:", now)
duration, _ := time.ParseDuration("-1.5h")
then := now.Add(duration)
fmt.Println("then:", then)
}
https://play.golang.org/p/63p-T9uFcZo

Resources