Search code examples
gotimeunix-timestampdata-conversion

Golang - Find month and day of month from YearDay int32


I have a fixed data structure given to me that has the fields YearDay and TimeOfDay. YearDay is the number of days that have passed in the current year, TimeOfDay is the number of seconds that have passed in the current day (up to 86400). YearDay is an int32, while TimeOfDay is a float64.

I want to convert this to time.Now().UnixNano() form but am unsure how to convert it. The time module has a YearDay(), but no inverse function given an yearDay (int32) (and probably a year), to give me the month and day in the month.

Ideally I'd like to somehow parse

d := time.Date(time.Year(), month, day, hour, min, sec, ms, time.UTC)

where month, day, hour, min, sec, ms was somehow predetermined, or something of equivalent that I can convert easily to any form I'd like (but mainly UnixNano()).

My best imagination would be a complicated switch statement that subtracted 31, 28(29), 30, 31 ... and to see when the int is finally negative to find the month and day, but it would have to be two switch statements with a leap year check to choose which switch block to use, while doing several remainder calculations on TimeOfDay. Is there a simpler and cleaner way?

Edit: I ended up making the following function while playing around with it, but I'll definitely be using Icza's solution. Nice to know days can overflow. Thanks!

func findMonthAndDay(yearDay int32) (int32, int32) {
    year := time.Now().Year()
    isLeapYear := year%400 == 0 || year%4 == 0 && year%100 != 0 // Calculates if current year is leapyear

    // Determines which array to send to for loop
    var monthsOfYear [12]int32
    if isLeapYear {
        monthsOfYear = [12]int32{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
    } else {
        monthsOfYear = [12]int32{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
    }

    var currentMonth int32
    var currentDayInMonth int32

    // Loop through array of months
    for i := range monthsOfYear {
        // If yearDay - next month #OfDays positive, not correct month
        if yearDay-monthsOfYear[i] > 0 {
            // Subtract month #OfDays and continue
            yearDay = yearDay - monthsOfYear[i]

        } else {
            currentMonth = int32(i + 1) // Month found (+1 due to index at 0)
            currentDayInMonth = yearDay // Remainder of YearDay is day in month
            break
        }
    }
    return currentMonth, currentDayInMonth
}

Solution

  • You may use Time.AddDate() to add the number of days to a time.Time value. It is OK to add days greater than 31, the implementation normalizes the result.

    And convert TimeOfDay to time.Duration and use Time.Add() to add it. When converting to time.Duration, we may multiply it by 1e9 to get number of nanoseconds, so fractional seconds will be retained.

    Example:

    t := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
    fmt.Println(t)
    
    var yearDay int32 = 100
    var timeOfDay float64 = 70000.5
    
    t = t.AddDate(0, 0, int(yearDay))
    t = t.Add(time.Duration(timeOfDay * 1e9))
    
    fmt.Println(t)
    fmt.Println("Unix:", t.Unix())
    fmt.Println("UnixNano:", t.UnixNano())
    

    Output (try it on the Go Playground):

    2020-01-01 00:00:00 +0000 UTC
    2020-04-10 19:26:40.5 +0000 UTC
    Unix: 1586546800
    UnixNano: 1586546800500000000